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

Commit 8a439d0a authored by Bonian Chen's avatar Bonian Chen
Browse files

[Settings] Code refactor for SIM change detection

Code refactor for maintainance:
  Move subscription change monitor out of ResetnetworkConfirm

Bug: 259611847
Test: local test, auto testing
Change-Id: I97a251ced4435d0cc390b40028edef07fd71e24f
parent 1439ba42
Loading
Loading
Loading
Loading
+20 −63
Original line number Diff line number Diff line
@@ -61,6 +61,7 @@ public class ResetNetworkConfirm extends InstrumentedFragment {
    @VisibleForTesting ResetNetworkRequest mResetNetworkRequest;
    private ProgressDialog mProgressDialog;
    private AlertDialog mAlertDialog;
    @VisibleForTesting ResetSubscriptionContract mResetSubscriptionContract;
    private OnSubscriptionsChangedListener mSubscriptionsChangedListener;

    /**
@@ -130,17 +131,12 @@ public class ResetNetworkConfirm extends InstrumentedFragment {
            }

            // abandon execution if subscription no longer active
            int subId = mResetNetworkRequest.getResetApnSubId();
            if (subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
                SubscriptionManager mgr = getSubscriptionManager();
                // always remove listener
                stopMonitorSubscriptionChange(mgr);
                if (!isSubscriptionRemainActive(mgr, subId)) {
                    Log.w(TAG, "subId " + subId + " disappear when confirm");
                    mActivity.finish();
            Integer subId = mResetSubscriptionContract.getAnyMissingSubscriptionId();
            if (subId != null) {
                Log.w(TAG, "subId " + subId + " no longer active");
                getActivity().onBackPressed();
                return;
            }
            }

            // Should dismiss the progress dialog firstly if it is showing
            // Or not the progress dialog maybe not dismissed in fast clicking.
@@ -186,7 +182,7 @@ public class ResetNetworkConfirm extends InstrumentedFragment {
            Bundle savedInstanceState) {
        View view = (new ResetNetworkRestrictionViewBuilder(mActivity)).build();
        if (view != null) {
            stopMonitorSubscriptionChange(getSubscriptionManager());
            mResetSubscriptionContract.close();
            Log.w(TAG, "Access deny.");
            return view;
        }
@@ -208,13 +204,15 @@ public class ResetNetworkConfirm extends InstrumentedFragment {

        mActivity = getActivity();

        if (mResetNetworkRequest.getResetApnSubId()
                == ResetNetworkRequest.INVALID_SUBSCRIPTION_ID) {
            return;
        mResetSubscriptionContract = new ResetSubscriptionContract(getContext(),
                mResetNetworkRequest) {
            @Override
            public void onSubscriptionInactive(int subscriptionId) {
                // close UI if subscription no longer active
                Log.w(TAG, "subId " + subscriptionId + " no longer active.");
                getActivity().onBackPressed();
            }
        // close confirmation dialog when reset specific subscription
        // but removed priori to the confirmation button been pressed
        startMonitorSubscriptionChange(getSubscriptionManager());
        };
    }

    @Override
@@ -223,63 +221,22 @@ public class ResetNetworkConfirm extends InstrumentedFragment {
        mResetNetworkRequest.writeIntoBundle(outState);
    }

    private SubscriptionManager getSubscriptionManager() {
        SubscriptionManager mgr = mActivity.getSystemService(SubscriptionManager.class);
        if (mgr == null) {
            Log.w(TAG, "No SubscriptionManager");
        }
        return mgr;
    }

    private void startMonitorSubscriptionChange(SubscriptionManager mgr) {
        if (mgr == null) {
            return;
        }
        // update monitor listener
        mSubscriptionsChangedListener = new OnSubscriptionsChangedListener(
                Looper.getMainLooper()) {
            @Override
            public void onSubscriptionsChanged() {
                int subId = mResetNetworkRequest.getResetApnSubId();
                SubscriptionManager mgr = getSubscriptionManager();
                if (isSubscriptionRemainActive(mgr, subId)) {
                    return;
                }
                // close UI if subscription no longer active
                Log.w(TAG, "subId " + subId + " no longer active.");
                stopMonitorSubscriptionChange(mgr);
                mActivity.finish();
            }
        };
        mgr.addOnSubscriptionsChangedListener(
                mActivity.getMainExecutor(), mSubscriptionsChangedListener);
    }

    private boolean isSubscriptionRemainActive(SubscriptionManager mgr, int subscriptionId) {
        return (mgr == null) ? false : (mgr.getActiveSubscriptionInfo(subscriptionId) != null);
    }

    private void stopMonitorSubscriptionChange(SubscriptionManager mgr) {
        if ((mgr == null) || (mSubscriptionsChangedListener == null)) {
            return;
        }
        mgr.removeOnSubscriptionsChangedListener(mSubscriptionsChangedListener);
        mSubscriptionsChangedListener = null;
    }

    @Override
    public void onDestroy() {
        if (mResetNetworkTask != null) {
            mResetNetworkTask.cancel(true /* mayInterruptIfRunning */);
            mResetNetworkTask = null;
        }
        if (mResetSubscriptionContract != null) {
            mResetSubscriptionContract.close();
            mResetSubscriptionContract = null;
        }
        if (mProgressDialog != null) {
            mProgressDialog.dismiss();
        }
        if (mAlertDialog != null) {
            mAlertDialog.dismiss();
        }
        stopMonitorSubscriptionChange(getSubscriptionManager());
        super.onDestroy();
    }

+157 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 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.settings;

import android.content.Context;
import android.telephony.SubscriptionManager;
import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
import android.util.Log;

import androidx.annotation.VisibleForTesting;

import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.IntStream;

/**
 * A Class monitoring the availability of subscription IDs provided within reset request.
 *
 * This is to detect the situation when user changing SIM card during the presenting of
 * confirmation UI.
 */
public class ResetSubscriptionContract implements AutoCloseable {
    private static final String TAG = "ResetSubscriptionContract";

    private final Context mContext;
    private ExecutorService mExecutorService;
    private final int [] mResetSubscriptionIds;
    @VisibleForTesting
    protected OnSubscriptionsChangedListener mSubscriptionsChangedListener;
    private AtomicBoolean mSubscriptionsUpdateNotify = new AtomicBoolean();

    /**
     * Constructor
     * @param context Context
     * @param resetRequest the request object for perform network reset operation.
     */
    public ResetSubscriptionContract(Context context, ResetNetworkRequest resetRequest) {
        mContext = context;
        // Only keeps specific subscription ID required to perform reset operation
        IntStream subIdStream = IntStream.of(
                resetRequest.getResetTelephonyAndNetworkPolicyManager()
                , resetRequest.getResetApnSubId());
        mResetSubscriptionIds = subIdStream.sorted().distinct()
                .filter(id -> SubscriptionManager.isUsableSubscriptionId(id))
                .toArray();

        if (mResetSubscriptionIds.length <= 0) {
            return;
        }

        // Monitoring callback through background thread
        mExecutorService = Executors.newSingleThreadExecutor();
        startMonitorSubscriptionChange();
    }

    /**
     * A method for detecting if there's any subscription under monitor no longer active.
     * @return subscription ID which is no longer active.
     */
    public Integer getAnyMissingSubscriptionId() {
        if (mResetSubscriptionIds.length <= 0) {
            return null;
        }
        SubscriptionManager mgr = getSubscriptionManager();
        if (mgr == null) {
            Log.w(TAG, "Fail to access subscription manager");
            return mResetSubscriptionIds[0];
        }
        for (int idx = 0; idx < mResetSubscriptionIds.length; idx++) {
            int subId = mResetSubscriptionIds[idx];
            if (mgr.getActiveSubscriptionInfo(subId) == null) {
                Log.w(TAG, "SubId " + subId + " no longer active.");
                return subId;
            }
        }
        return null;
    }

    /**
     * Async callback when detecting if there's any subscription under monitor no longer active.
     * @param subscriptionId subscription ID which is no longer active.
     */
    public void onSubscriptionInactive(int subscriptionId) {}

    @VisibleForTesting
    protected SubscriptionManager getSubscriptionManager() {
        return mContext.getSystemService(SubscriptionManager.class);
    }

    @VisibleForTesting
    protected OnSubscriptionsChangedListener getChangeListener() {
        return new OnSubscriptionsChangedListener() {
            @Override
            public void onSubscriptionsChanged() {
                /**
                 * Reducing the processing time on main UI thread through a flag.
                 * Once flag get into false, which means latest callback has been
                 * processed.
                 */
                mSubscriptionsUpdateNotify.set(true);

                // Back to main UI thread
                mContext.getMainExecutor().execute(() -> {
                    // Remove notifications and perform checking.
                    if (mSubscriptionsUpdateNotify.getAndSet(false)) {
                        Integer subId = getAnyMissingSubscriptionId();
                        if (subId != null) {
                            onSubscriptionInactive(subId);
                        }
                    }
                });
            }
        };
    }

    private void startMonitorSubscriptionChange() {
        SubscriptionManager mgr = getSubscriptionManager();
        if (mgr == null) {
            return;
        }
        // update monitor listener
        mSubscriptionsChangedListener = getChangeListener();

        mgr.addOnSubscriptionsChangedListener(
                mExecutorService, mSubscriptionsChangedListener);
    }

    // Implementation of AutoCloseable
    public void close() {
        if (mExecutorService == null) {
            return;
        }
        // Stop monitoring subscription change
        SubscriptionManager mgr = getSubscriptionManager();
        if (mgr != null) {
            mgr.removeOnSubscriptionsChangedListener(mSubscriptionsChangedListener);
        }
        // Release Executor
        mExecutorService.shutdownNow();
        mExecutorService = null;
    }
}
+8 −0
Original line number Diff line number Diff line
@@ -74,6 +74,14 @@ public class ResetNetworkConfirmTest {
    public void testResetNetworkData_notResetEsim() {
        mResetNetworkConfirm.mResetNetworkRequest =
                new ResetNetworkRequest(ResetNetworkRequest.RESET_NONE);
        mResetNetworkConfirm.mResetSubscriptionContract =
                new ResetSubscriptionContract(mActivity,
                mResetNetworkConfirm.mResetNetworkRequest) {
            @Override
            public void onSubscriptionInactive(int subscriptionId) {
                mActivity.onBackPressed();
            }
        };

        mResetNetworkConfirm.mFinalClickListener.onClick(null /* View */);
        Robolectric.getBackgroundThreadScheduler().advanceToLastPostedRunnable();
+109 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 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.settings;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;

import android.content.Context;
import android.os.Bundle;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

@RunWith(AndroidJUnit4.class)
public class ResetSubscriptionContractTest {

    private static final int SUB_ID_1 = 3;
    private static final int SUB_ID_2 = 8;

    @Mock
    private SubscriptionManager mSubscriptionManager;
    @Mock
    private OnSubscriptionsChangedListener mOnSubscriptionsChangedListener;
    @Mock
    private SubscriptionInfo mSubscriptionInfo1;
    @Mock
    private SubscriptionInfo mSubscriptionInfo2;

    private Context mContext;
    private ResetNetworkRequest mRequestArgs;

    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);

        mContext = spy(ApplicationProvider.getApplicationContext());
        mRequestArgs = new ResetNetworkRequest(new Bundle());
    }

    private ResetSubscriptionContract createTestObject() {
        return new ResetSubscriptionContract(mContext, mRequestArgs) {
            @Override
            protected SubscriptionManager getSubscriptionManager() {
                return mSubscriptionManager;
            }
            @Override
            protected OnSubscriptionsChangedListener getChangeListener() {
                return mOnSubscriptionsChangedListener;
            }
        };
    }

    @Test
    public void getAnyMissingSubscriptionId_returnNull_whenNoSubscriptionChange() {
        mRequestArgs.setResetTelephonyAndNetworkPolicyManager(SUB_ID_1);
        doReturn(mSubscriptionInfo1).when(mSubscriptionManager)
                .getActiveSubscriptionInfo(SUB_ID_1);
        mRequestArgs.setResetApn(SUB_ID_2);
        doReturn(mSubscriptionInfo2).when(mSubscriptionManager)
                .getActiveSubscriptionInfo(SUB_ID_2);

        ResetSubscriptionContract target = createTestObject();

        verify(mSubscriptionManager).addOnSubscriptionsChangedListener(any(), any());

        assertNull(target.getAnyMissingSubscriptionId());
    }

    @Test
    public void getAnyMissingSubscriptionId_returnSubId_whenSubscriptionNotActive() {
        mRequestArgs.setResetTelephonyAndNetworkPolicyManager(SUB_ID_1);
        doReturn(mSubscriptionInfo1).when(mSubscriptionManager)
                .getActiveSubscriptionInfo(SUB_ID_1);
        mRequestArgs.setResetApn(SUB_ID_2);
        doReturn(null).when(mSubscriptionManager)
                .getActiveSubscriptionInfo(SUB_ID_2);

        ResetSubscriptionContract target = createTestObject();

        verify(mSubscriptionManager).addOnSubscriptionsChangedListener(any(), any());

        assertEquals(target.getAnyMissingSubscriptionId(), new Integer(SUB_ID_2));
    }
}