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

Commit 0ef8df6d authored by Treehugger Robot's avatar Treehugger Robot Committed by Gerrit Code Review
Browse files

Merge "Extract SimChangeListener to its own file"

parents f854c903 227648f3
Loading
Loading
Loading
Loading
+53 −124
Original line number Diff line number Diff line
@@ -77,6 +77,7 @@ import com.android.server.connectivity.tethering.IControlsTethering;
import com.android.server.connectivity.tethering.IPv6TetheringCoordinator;
import com.android.server.connectivity.tethering.IPv6TetheringInterfaceServices;
import com.android.server.connectivity.tethering.OffloadController;
import com.android.server.connectivity.tethering.SimChangeListener;
import com.android.server.connectivity.tethering.TetherInterfaceStateMachine;
import com.android.server.connectivity.tethering.TetheringConfiguration;
import com.android.server.connectivity.tethering.UpstreamNetworkMonitor;
@@ -157,6 +158,7 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering
    private final OffloadController mOffloadController;
    private final UpstreamNetworkMonitor mUpstreamNetworkMonitor;
    private final HashSet<TetherInterfaceStateMachine> mForwardedDownstreams;
    private final SimChangeListener mSimChange;

    private volatile TetheringConfiguration mConfig;
    private String mCurrentUpstreamIface;
@@ -190,6 +192,8 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering
        mUpstreamNetworkMonitor = new UpstreamNetworkMonitor(
                mContext, mTetherMasterSM, TetherMasterSM.EVENT_UPSTREAM_CALLBACK);
        mForwardedDownstreams = new HashSet<>();
        mSimChange = new SimChangeListener(
                mContext, mTetherMasterSM.getHandler(), () -> reevaluateSimCardProvisioning());

        mStateReceiver = new StateReceiver();
        IntentFilter filter = new IntentFilter();
@@ -352,6 +356,20 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering
        return (provisionApp.length == 2);
    }

    // Used by the SIM card change observation code.
    // TODO: De-duplicate above code.
    private boolean hasMobileHotspotProvisionApp() {
        try {
            if (!mContext.getResources().getString(com.android.internal.R.string.
                    config_mobile_hotspot_provision_app_no_ui).isEmpty()) {
                Log.d(TAG, "re-evaluate provisioning");
                return true;
            }
        } catch (Resources.NotFoundException e) {}
        Log.d(TAG, "no prov-check needed for new SIM");
        return false;
    }

    /**
     * Enables or disables tethering for the given type. This should only be called once
     * provisioning has succeeded or is not necessary. It will also schedule provisioning rechecks
@@ -526,6 +544,16 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering
        }
    }

    // Used by the SIM card change observation code.
    // TODO: De-duplicate with above code, where possible.
    private void startProvisionIntent(int tetherType) {
        final Intent startProvIntent = new Intent();
        startProvIntent.putExtra(ConnectivityManager.EXTRA_ADD_TETHER_TYPE, tetherType);
        startProvIntent.putExtra(ConnectivityManager.EXTRA_RUN_PROVISION, true);
        startProvIntent.setComponent(TETHER_SERVICE);
        mContext.startServiceAsUser(startProvIntent, UserHandle.CURRENT);
    }

    public int tether(String iface) {
        return tether(iface, IControlsTethering.STATE_TETHERED);
    }
@@ -995,6 +1023,29 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering
        return false;
    }

    private void reevaluateSimCardProvisioning() {
        if (!hasMobileHotspotProvisionApp()) return;

        ArrayList<Integer> tethered = new ArrayList<>();
        synchronized (mPublicSync) {
            for (int i = 0; i < mTetherStates.size(); i++) {
                TetherState tetherState = mTetherStates.valueAt(i);
                if (tetherState.lastState != IControlsTethering.STATE_TETHERED) {
                    continue;  // Skip interfaces that aren't tethered.
                }
                String iface = mTetherStates.keyAt(i);
                int interfaceType = ifaceNameToType(iface);
                if (interfaceType != ConnectivityManager.TETHERING_INVALID) {
                    tethered.add(interfaceType);
                }
            }
        }

        for (int tetherType : tethered) {
            startProvisionIntent(tetherType);
        }
    }

    class TetherMasterSM extends StateMachine {
        private static final int BASE_MASTER                    = Protocol.BASE_TETHERING;
        // an interface SM has requested Tethering/Local Hotspot
@@ -1268,127 +1319,6 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering
            }
        }

        private class SimChangeListener {
            private final Context mContext;
            private final AtomicInteger mSimBcastGenerationNumber;
            private BroadcastReceiver mBroadcastReceiver;

            SimChangeListener(Context ctx) {
                mContext = ctx;
                mSimBcastGenerationNumber = new AtomicInteger(0);
            }

            public int generationNumber() {
                return mSimBcastGenerationNumber.get();
            }

            public void startListening() {
                if (DBG) Log.d(TAG, "startListening for SIM changes");

                if (mBroadcastReceiver != null) return;

                mBroadcastReceiver = new SimChangeBroadcastReceiver(
                        mSimBcastGenerationNumber.incrementAndGet());
                final IntentFilter filter = new IntentFilter();
                filter.addAction(TelephonyIntents.ACTION_SIM_STATE_CHANGED);

                mContext.registerReceiver(mBroadcastReceiver, filter, null,
                        mTetherMasterSM.getHandler());
            }

            public void stopListening() {
                if (DBG) Log.d(TAG, "stopListening for SIM changes");

                if (mBroadcastReceiver == null) return;

                mSimBcastGenerationNumber.incrementAndGet();
                mContext.unregisterReceiver(mBroadcastReceiver);
                mBroadcastReceiver = null;
            }

            public boolean hasMobileHotspotProvisionApp() {
                try {
                    if (!mContext.getResources().getString(com.android.internal.R.string.
                            config_mobile_hotspot_provision_app_no_ui).isEmpty()) {
                        Log.d(TAG, "re-evaluate provisioning");
                        return true;
                    }
                } catch (Resources.NotFoundException e) {}
                Log.d(TAG, "no prov-check needed for new SIM");
                return false;
            }

            private boolean isSimCardLoaded(String state) {
                return IccCardConstants.INTENT_VALUE_ICC_LOADED.equals(state);
            }

            private void startProvisionIntent(int tetherType) {
                final Intent startProvIntent = new Intent();
                startProvIntent.putExtra(ConnectivityManager.EXTRA_ADD_TETHER_TYPE, tetherType);
                startProvIntent.putExtra(ConnectivityManager.EXTRA_RUN_PROVISION, true);
                startProvIntent.setComponent(TETHER_SERVICE);
                mContext.startServiceAsUser(startProvIntent, UserHandle.CURRENT);
            }

            private class SimChangeBroadcastReceiver extends BroadcastReceiver {
                // used to verify this receiver is still current
                final private int mGenerationNumber;

                // used to check the sim state transition from non-loaded to loaded
                private boolean mSimNotLoadedSeen = false;

                public SimChangeBroadcastReceiver(int generationNumber) {
                    mGenerationNumber = generationNumber;
                }

                @Override
                public void onReceive(Context context, Intent intent) {
                    final int currentGenerationNumber = mSimBcastGenerationNumber.get();

                    if (DBG) {
                        Log.d(TAG, "simchange mGenerationNumber=" + mGenerationNumber +
                                ", current generationNumber=" + currentGenerationNumber);
                    }
                    if (mGenerationNumber != currentGenerationNumber) return;

                    final String state = intent.getStringExtra(
                            IccCardConstants.INTENT_KEY_ICC_STATE);
                    Log.d(TAG, "got Sim changed to state " + state + ", mSimNotLoadedSeen=" +
                            mSimNotLoadedSeen);

                    if (!isSimCardLoaded(state)) {
                        if (!mSimNotLoadedSeen) mSimNotLoadedSeen = true;
                        return;
                    }

                    if (isSimCardLoaded(state) && mSimNotLoadedSeen) {
                        mSimNotLoadedSeen = false;

                        if (!hasMobileHotspotProvisionApp()) return;

                        ArrayList<Integer> tethered = new ArrayList<Integer>();
                        synchronized (mPublicSync) {
                            for (int i = 0; i < mTetherStates.size(); i++) {
                                TetherState tetherState = mTetherStates.valueAt(i);
                                if (tetherState.lastState != IControlsTethering.STATE_TETHERED) {
                                    continue;  // Skip interfaces that aren't tethered.
                                }
                                String iface = mTetherStates.keyAt(i);
                                int interfaceType = ifaceNameToType(iface);
                                if (interfaceType != ConnectivityManager.TETHERING_INVALID) {
                                    tethered.add(new Integer(interfaceType));
                                }
                            }
                        }

                        for (int tetherType : tethered) {
                            startProvisionIntent(tetherType);
                        }
                    }
                }
            }
        }

        private void handleInterfaceServingStateActive(int mode, TetherInterfaceStateMachine who) {
            if (mNotifyList.indexOf(who) < 0) {
                mNotifyList.add(who);
@@ -1434,7 +1364,6 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering
        }

        class TetherModeAliveState extends TetherMasterUtilState {
            final SimChangeListener simChange = new SimChangeListener(mContext);
            boolean mUpstreamWanted = false;
            boolean mTryCell = true;

@@ -1442,7 +1371,7 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering
            public void enter() {
                // TODO: examine if we should check the return value.
                turnOnMasterTetherSettings(); // may transition us out
                simChange.startListening();
                mSimChange.startListening();
                mUpstreamNetworkMonitor.start();
                mOffloadController.start();

@@ -1458,7 +1387,7 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering
                mOffloadController.stop();
                unrequestUpstreamMobileConnection();
                mUpstreamNetworkMonitor.stop();
                simChange.stopListening();
                mSimChange.stopListening();
                notifyTetheredOfNewUpstreamIface(null);
                handleNewUpstreamNetworkState(null);
            }
+124 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2017 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 com.android.server.connectivity.tethering;

import static com.android.internal.telephony.IccCardConstants.INTENT_VALUE_ICC_LOADED;
import static com.android.internal.telephony.IccCardConstants.INTENT_KEY_ICC_STATE;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Handler;
import android.util.Log;

import com.android.internal.telephony.TelephonyIntents;

import java.util.concurrent.atomic.AtomicInteger;


/**
 * A utility class that runs the provided callback on the provided handler when
 * observing a new SIM card having been loaded.
 *
 * @hide
 */
public class SimChangeListener {
    private static final String TAG = SimChangeListener.class.getSimpleName();
    private static final boolean DBG = false;

    private final Context mContext;
    private final Handler mTarget;
    private final AtomicInteger mSimBcastGenerationNumber;
    private final Runnable mCallback;
    private BroadcastReceiver mBroadcastReceiver;

    public SimChangeListener(Context ctx, Handler handler, Runnable onSimCardLoadedCallback) {
        mContext = ctx;
        mTarget = handler;
        mCallback = onSimCardLoadedCallback;
        mSimBcastGenerationNumber = new AtomicInteger(0);
    }

    public int generationNumber() {
        return mSimBcastGenerationNumber.get();
    }

    public void startListening() {
        if (DBG) Log.d(TAG, "startListening for SIM changes");

        if (mBroadcastReceiver != null) return;

        mBroadcastReceiver = new SimChangeBroadcastReceiver(
                mSimBcastGenerationNumber.incrementAndGet());
        final IntentFilter filter = new IntentFilter();
        filter.addAction(TelephonyIntents.ACTION_SIM_STATE_CHANGED);

        mContext.registerReceiver(mBroadcastReceiver, filter, null, mTarget);
    }

    public void stopListening() {
        if (DBG) Log.d(TAG, "stopListening for SIM changes");

        if (mBroadcastReceiver == null) return;

        mSimBcastGenerationNumber.incrementAndGet();
        mContext.unregisterReceiver(mBroadcastReceiver);
        mBroadcastReceiver = null;
    }

    private boolean isSimCardLoaded(String state) {
        return INTENT_VALUE_ICC_LOADED.equals(state);
    }

    private class SimChangeBroadcastReceiver extends BroadcastReceiver {
        // used to verify this receiver is still current
        final private int mGenerationNumber;

        // used to check the sim state transition from non-loaded to loaded
        private boolean mSimNotLoadedSeen = false;

        public SimChangeBroadcastReceiver(int generationNumber) {
            mGenerationNumber = generationNumber;
        }

        @Override
        public void onReceive(Context context, Intent intent) {
            final int currentGenerationNumber = mSimBcastGenerationNumber.get();

            if (DBG) {
                Log.d(TAG, "simchange mGenerationNumber=" + mGenerationNumber +
                        ", current generationNumber=" + currentGenerationNumber);
            }
            if (mGenerationNumber != currentGenerationNumber) return;

            final String state = intent.getStringExtra(INTENT_KEY_ICC_STATE);
            Log.d(TAG, "got Sim changed to state " + state + ", mSimNotLoadedSeen=" +
                    mSimNotLoadedSeen);

            if (!isSimCardLoaded(state)) {
                mSimNotLoadedSeen = true;
                return;
            }

            if (mSimNotLoadedSeen) {
                mSimNotLoadedSeen = false;
                mCallback.run();
            }
        }
    }
}
+134 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2017 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 com.android.server.connectivity.tethering;

import static com.android.internal.telephony.IccCardConstants.INTENT_VALUE_ICC_ABSENT;
import static com.android.internal.telephony.IccCardConstants.INTENT_VALUE_ICC_LOADED;
import static com.android.internal.telephony.IccCardConstants.INTENT_KEY_ICC_STATE;
import static com.android.internal.telephony.TelephonyIntents.ACTION_SIM_STATE_CHANGED;

import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.reset;

import android.content.Context;
import android.content.Intent;
import android.os.Handler;
import android.os.Looper;
import android.os.UserHandle;

import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;

import com.android.internal.util.test.BroadcastInterceptingContext;

import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.runner.RunWith;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;


@RunWith(AndroidJUnit4.class)
@SmallTest
public class SimChangeListenerTest {
    private static final int EVENT_UNM_UPDATE = 1;

    @Mock private Context mContext;
    private BroadcastInterceptingContext mServiceContext;
    private Handler mHandler;
    private SimChangeListener mSCL;
    private int mCallbackCount;

    private void doCallback() { mCallbackCount++; }

    private class MockContext extends BroadcastInterceptingContext {
        MockContext(Context base) {
            super(base);
        }
    }

    @BeforeClass
    public static void setUpBeforeClass() throws Exception {
        if (Looper.myLooper() == null) {
            Looper.prepare();
        }
    }

    @Before public void setUp() throws Exception {
        MockitoAnnotations.initMocks(this);
        reset(mContext);
        mServiceContext = new MockContext(mContext);
        mHandler = new Handler(Looper.myLooper());
        mCallbackCount = 0;
        mSCL = new SimChangeListener(mServiceContext, mHandler, () -> doCallback());
    }

    @After public void tearDown() throws Exception {
        if (mSCL != null) {
            mSCL.stopListening();
            mSCL = null;
        }
    }

    private void sendSimStateChangeIntent(String state) {
        final Intent intent = new Intent(ACTION_SIM_STATE_CHANGED);
        intent.putExtra(INTENT_KEY_ICC_STATE, state);
        mServiceContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
    }

    @Test
    public void testNotSeenFollowedBySeenCallsCallback() {
        mSCL.startListening();

        sendSimStateChangeIntent(INTENT_VALUE_ICC_ABSENT);
        sendSimStateChangeIntent(INTENT_VALUE_ICC_LOADED);
        assertEquals(1, mCallbackCount);

        sendSimStateChangeIntent(INTENT_VALUE_ICC_ABSENT);
        sendSimStateChangeIntent(INTENT_VALUE_ICC_LOADED);
        assertEquals(2, mCallbackCount);

        mSCL.stopListening();
    }

    @Test
    public void testNotListeningDoesNotCallback() {
        sendSimStateChangeIntent(INTENT_VALUE_ICC_ABSENT);
        sendSimStateChangeIntent(INTENT_VALUE_ICC_LOADED);
        assertEquals(0, mCallbackCount);

        sendSimStateChangeIntent(INTENT_VALUE_ICC_ABSENT);
        sendSimStateChangeIntent(INTENT_VALUE_ICC_LOADED);
        assertEquals(0, mCallbackCount);
    }

    @Test
    public void testSeenOnlyDoesNotCallback() {
        mSCL.startListening();

        sendSimStateChangeIntent(INTENT_VALUE_ICC_LOADED);
        assertEquals(0, mCallbackCount);

        sendSimStateChangeIntent(INTENT_VALUE_ICC_LOADED);
        assertEquals(0, mCallbackCount);

        mSCL.stopListening();
    }
}