diff --git a/src/com/android/settings/ResetNetworkConfirm.java b/src/com/android/settings/ResetNetworkConfirm.java index 0cd94a53105..c707b96a328 100644 --- a/src/com/android/settings/ResetNetworkConfirm.java +++ b/src/com/android/settings/ResetNetworkConfirm.java @@ -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,16 +131,11 @@ 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(); - return; - } + 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 @@ -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; - } - // close confirmation dialog when reset specific subscription - // but removed priori to the confirmation button been pressed - startMonitorSubscriptionChange(getSubscriptionManager()); + 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(); + } + }; } @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(); } diff --git a/src/com/android/settings/ResetSubscriptionContract.java b/src/com/android/settings/ResetSubscriptionContract.java new file mode 100644 index 00000000000..580e907707b --- /dev/null +++ b/src/com/android/settings/ResetSubscriptionContract.java @@ -0,0 +1,157 @@ +/* + * 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; + } +} diff --git a/tests/robotests/src/com/android/settings/ResetNetworkConfirmTest.java b/tests/robotests/src/com/android/settings/ResetNetworkConfirmTest.java index 5dad40d8127..0bab303cf00 100644 --- a/tests/robotests/src/com/android/settings/ResetNetworkConfirmTest.java +++ b/tests/robotests/src/com/android/settings/ResetNetworkConfirmTest.java @@ -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(); diff --git a/tests/unit/src/com/android/settings/ResetSubscriptionContractTest.java b/tests/unit/src/com/android/settings/ResetSubscriptionContractTest.java new file mode 100644 index 00000000000..4443304d696 --- /dev/null +++ b/tests/unit/src/com/android/settings/ResetSubscriptionContractTest.java @@ -0,0 +1,109 @@ +/* + * 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)); + } +}