Merge "[Settings] Code refactor for SIM change detection"
diff --git a/src/com/android/settings/ResetNetworkConfirm.java b/src/com/android/settings/ResetNetworkConfirm.java
index 0cd94a5..c707b96 100644
--- a/src/com/android/settings/ResetNetworkConfirm.java
+++ b/src/com/android/settings/ResetNetworkConfirm.java
@@ -61,6 +61,7 @@
@VisibleForTesting ResetNetworkRequest mResetNetworkRequest;
private ProgressDialog mProgressDialog;
private AlertDialog mAlertDialog;
+ @VisibleForTesting ResetSubscriptionContract mResetSubscriptionContract;
private OnSubscriptionsChangedListener mSubscriptionsChangedListener;
/**
@@ -130,16 +131,11 @@
}
// 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 @@
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 @@
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 @@
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 0000000..580e907
--- /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 5dad40d..0bab303 100644
--- a/tests/robotests/src/com/android/settings/ResetNetworkConfirmTest.java
+++ b/tests/robotests/src/com/android/settings/ResetNetworkConfirmTest.java
@@ -74,6 +74,14 @@
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 0000000..4443304
--- /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));
+ }
+}