From 28b85b5810e7abe74ebfec1f38f7b672c056abc0 Mon Sep 17 00:00:00 2001 From: Chaohui Wang Date: Tue, 19 Dec 2023 18:10:12 +0800 Subject: [PATCH] Refresh "Choose network" summary when service state changes This summary display service connection state, which should be refreshed when service state changes. Fix: 313026209 Test: manual - on Mobile Settings Test: unit test Change-Id: I6ca2f89e05f21460a7db055f037919b6ebd19182 --- .../network/AllowedNetworkTypesListener.java | 4 + .../network/helper/ServiceStateStatus.java | 5 +- .../telephony/AllowedNetworkTypesFlow.kt | 39 +++++ .../network/telephony/CallStateFlow.kt | 19 +-- .../network/telephony/ServiceStateFlow.kt | 35 +++++ .../network/telephony/TelephonyRepository.kt | 44 ++++++ ...NetworkSelectPagePreferenceController.java | 139 ------------------ ...enNetworkSelectPagePreferenceController.kt | 107 ++++++++++++++ .../telephony/AllowedNetworkTypesFlowTest.kt | 113 ++++++++++++++ .../network/telephony/ServiceStateFlowTest.kt | 80 ++++++++++ .../telephony/TelephonyRepositoryTest.kt | 76 ++++++++++ ...tworkSelectPagePreferenceControllerTest.kt | 118 +++++++++++++++ ...orkSelectPagePreferenceControllerTest.java | 124 ---------------- 13 files changed, 622 insertions(+), 281 deletions(-) create mode 100644 src/com/android/settings/network/telephony/AllowedNetworkTypesFlow.kt create mode 100644 src/com/android/settings/network/telephony/ServiceStateFlow.kt create mode 100644 src/com/android/settings/network/telephony/TelephonyRepository.kt delete mode 100644 src/com/android/settings/network/telephony/gsm/OpenNetworkSelectPagePreferenceController.java create mode 100644 src/com/android/settings/network/telephony/gsm/OpenNetworkSelectPagePreferenceController.kt create mode 100644 tests/spa_unit/src/com/android/settings/network/telephony/AllowedNetworkTypesFlowTest.kt create mode 100644 tests/spa_unit/src/com/android/settings/network/telephony/ServiceStateFlowTest.kt create mode 100644 tests/spa_unit/src/com/android/settings/network/telephony/TelephonyRepositoryTest.kt create mode 100644 tests/spa_unit/src/com/android/settings/network/telephony/gsm/OpenNetworkSelectPagePreferenceControllerTest.kt delete mode 100644 tests/unit/src/com/android/settings/network/telephony/gsm/OpenNetworkSelectPagePreferenceControllerTest.java diff --git a/src/com/android/settings/network/AllowedNetworkTypesListener.java b/src/com/android/settings/network/AllowedNetworkTypesListener.java index 3d936646059..38c58715a87 100644 --- a/src/com/android/settings/network/AllowedNetworkTypesListener.java +++ b/src/com/android/settings/network/AllowedNetworkTypesListener.java @@ -27,7 +27,11 @@ import java.util.concurrent.Executor; /** * {@link TelephonyCallback} to listen to Allowed Network Types changed + * + * @deprecated Please use {@link com.android.settings.network.telephony.AllowedNetworkTypesFlowKt} + * instead. */ +@Deprecated public class AllowedNetworkTypesListener extends TelephonyCallback implements TelephonyCallback.AllowedNetworkTypesListener { private static final String LOG_TAG = "NetworkModeListener"; diff --git a/src/com/android/settings/network/helper/ServiceStateStatus.java b/src/com/android/settings/network/helper/ServiceStateStatus.java index 871884d1450..40e9e3fbcb5 100644 --- a/src/com/android/settings/network/helper/ServiceStateStatus.java +++ b/src/com/android/settings/network/helper/ServiceStateStatus.java @@ -19,7 +19,6 @@ package com.android.settings.network.helper; import android.telephony.ServiceState; import android.telephony.TelephonyCallback; import android.telephony.TelephonyManager; -import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; @@ -34,8 +33,10 @@ import java.util.function.Consumer; * Only got update when Lifecycle.State is considered as STARTED or RESUMED. * * {@code null} when status unknown. Other values are {@link ServiceState}. + * + * @deprecated Please us {@link com.android.settings.network.telephony.ServiceStateFlowKt} instead. */ -@VisibleForTesting +@Deprecated public class ServiceStateStatus extends LiveData { private static final String TAG = "ServiceStateStatus"; diff --git a/src/com/android/settings/network/telephony/AllowedNetworkTypesFlow.kt b/src/com/android/settings/network/telephony/AllowedNetworkTypesFlow.kt new file mode 100644 index 00000000000..cb507ab2d5b --- /dev/null +++ b/src/com/android/settings/network/telephony/AllowedNetworkTypesFlow.kt @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2023 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.network.telephony + +import android.content.Context +import android.telephony.TelephonyCallback +import android.telephony.TelephonyManager +import android.util.Log +import kotlinx.coroutines.flow.Flow + +private const val TAG = "AllowedNetworkTypesFlow" + +/** Creates an instance of a cold Flow for Allowed Network Types of given [subId]. */ +fun Context.allowedNetworkTypesFlow(subId: Int): Flow = telephonyCallbackFlow(subId) { + object : TelephonyCallback(), TelephonyCallback.AllowedNetworkTypesListener { + override fun onAllowedNetworkTypesChanged(reason: Int, allowedNetworkType: Long) { + if (reason == TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_USER || + reason == TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_CARRIER + ) { + trySend(allowedNetworkType) + Log.d(TAG, "[$subId] reason: $reason, allowedNetworkType: $allowedNetworkType") + } + } + } +} diff --git a/src/com/android/settings/network/telephony/CallStateFlow.kt b/src/com/android/settings/network/telephony/CallStateFlow.kt index 9d82602b9e3..f5164e072fa 100644 --- a/src/com/android/settings/network/telephony/CallStateFlow.kt +++ b/src/com/android/settings/network/telephony/CallStateFlow.kt @@ -18,28 +18,15 @@ package com.android.settings.network.telephony import android.content.Context import android.telephony.TelephonyCallback -import android.telephony.TelephonyManager -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.asExecutor -import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.callbackFlow -import kotlinx.coroutines.flow.conflate -import kotlinx.coroutines.flow.flowOn /** * Flow for call state. */ -fun Context.callStateFlow(subId: Int): Flow = callbackFlow { - val telephonyManager = getSystemService(TelephonyManager::class.java)!! - .createForSubscriptionId(subId) - - val callback = object : TelephonyCallback(), TelephonyCallback.CallStateListener { +fun Context.callStateFlow(subId: Int): Flow = telephonyCallbackFlow(subId) { + object : TelephonyCallback(), TelephonyCallback.CallStateListener { override fun onCallStateChanged(state: Int) { trySend(state) } } - telephonyManager.registerTelephonyCallback(Dispatchers.Default.asExecutor(), callback) - - awaitClose { telephonyManager.unregisterTelephonyCallback(callback) } -}.conflate().flowOn(Dispatchers.Default) +} diff --git a/src/com/android/settings/network/telephony/ServiceStateFlow.kt b/src/com/android/settings/network/telephony/ServiceStateFlow.kt new file mode 100644 index 00000000000..8770321f815 --- /dev/null +++ b/src/com/android/settings/network/telephony/ServiceStateFlow.kt @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2023 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.network.telephony + +import android.content.Context +import android.telephony.ServiceState +import android.telephony.TelephonyCallback +import android.util.Log +import kotlinx.coroutines.flow.Flow + +private const val TAG = "ServiceStateFlow" + +/** Creates an instance of a cold Flow for [ServiceState] of given [subId]. */ +fun Context.serviceStateFlow(subId: Int): Flow = telephonyCallbackFlow(subId) { + object : TelephonyCallback(), TelephonyCallback.ServiceStateListener { + override fun onServiceStateChanged(serviceState: ServiceState) { + trySend(serviceState) + Log.d(TAG, "[$subId] serviceState: $serviceState") + } + } +} diff --git a/src/com/android/settings/network/telephony/TelephonyRepository.kt b/src/com/android/settings/network/telephony/TelephonyRepository.kt new file mode 100644 index 00000000000..678aaac0ee4 --- /dev/null +++ b/src/com/android/settings/network/telephony/TelephonyRepository.kt @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2023 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.network.telephony + +import android.content.Context +import android.telephony.TelephonyCallback +import android.telephony.TelephonyManager +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.asExecutor +import kotlinx.coroutines.channels.ProducerScope +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.callbackFlow +import kotlinx.coroutines.flow.conflate +import kotlinx.coroutines.flow.flowOn + +/** Creates an instance of a cold Flow for Telephony callback of given [subId]. */ +fun Context.telephonyCallbackFlow( + subId: Int, + block: ProducerScope.() -> TelephonyCallback, +): Flow = callbackFlow { + val telephonyManager = getSystemService(TelephonyManager::class.java)!! + .createForSubscriptionId(subId) + + val callback = block() + + telephonyManager.registerTelephonyCallback(Dispatchers.Default.asExecutor(), callback) + + awaitClose { telephonyManager.unregisterTelephonyCallback(callback) } +}.conflate().flowOn(Dispatchers.Default) diff --git a/src/com/android/settings/network/telephony/gsm/OpenNetworkSelectPagePreferenceController.java b/src/com/android/settings/network/telephony/gsm/OpenNetworkSelectPagePreferenceController.java deleted file mode 100644 index 7bc0dc10e1b..00000000000 --- a/src/com/android/settings/network/telephony/gsm/OpenNetworkSelectPagePreferenceController.java +++ /dev/null @@ -1,139 +0,0 @@ -/* - * Copyright (C) 2018 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.network.telephony.gsm; - -import static androidx.lifecycle.Lifecycle.Event.ON_START; -import static androidx.lifecycle.Lifecycle.Event.ON_STOP; - -import static com.android.settings.Utils.SETTINGS_PACKAGE_NAME; - -import android.content.Context; -import android.content.Intent; -import android.provider.Settings; -import android.telephony.ServiceState; -import android.telephony.SubscriptionManager; -import android.telephony.TelephonyManager; - -import androidx.lifecycle.LifecycleObserver; -import androidx.lifecycle.OnLifecycleEvent; -import androidx.preference.Preference; -import androidx.preference.PreferenceScreen; - -import com.android.settings.R; -import com.android.settings.network.AllowedNetworkTypesListener; -import com.android.settings.network.telephony.MobileNetworkUtils; -import com.android.settings.network.telephony.TelephonyBasePreferenceController; - -/** - * Preference controller for "Open network select" - */ -public class OpenNetworkSelectPagePreferenceController extends - TelephonyBasePreferenceController implements - AutoSelectPreferenceController.OnNetworkSelectModeListener, LifecycleObserver { - - private TelephonyManager mTelephonyManager; - private Preference mPreference; - private PreferenceScreen mPreferenceScreen; - private AllowedNetworkTypesListener mAllowedNetworkTypesListener; - private int mCacheOfModeStatus; - - public OpenNetworkSelectPagePreferenceController(Context context, String key) { - super(context, key); - mTelephonyManager = context.getSystemService(TelephonyManager.class); - mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; - mCacheOfModeStatus = TelephonyManager.NETWORK_SELECTION_MODE_UNKNOWN; - mAllowedNetworkTypesListener = new AllowedNetworkTypesListener( - context.getMainExecutor()); - mAllowedNetworkTypesListener.setAllowedNetworkTypesListener( - () -> updatePreference()); - - } - - private void updatePreference() { - if (mPreferenceScreen != null) { - displayPreference(mPreferenceScreen); - } - if (mPreference != null) { - updateState(mPreference); - } - } - - @Override - public int getAvailabilityStatus(int subId) { - return MobileNetworkUtils.shouldDisplayNetworkSelectOptions(mContext, subId) - ? AVAILABLE - : CONDITIONALLY_UNAVAILABLE; - } - - @OnLifecycleEvent(ON_START) - public void onStart() { - mAllowedNetworkTypesListener.register(mContext, mSubId); - } - - @OnLifecycleEvent(ON_STOP) - public void onStop() { - mAllowedNetworkTypesListener.unregister(mContext, mSubId); - } - - @Override - public void displayPreference(PreferenceScreen screen) { - super.displayPreference(screen); - mPreferenceScreen = screen; - mPreference = screen.findPreference(getPreferenceKey()); - } - - @Override - public void updateState(Preference preference) { - super.updateState(preference); - preference.setEnabled(mCacheOfModeStatus - != TelephonyManager.NETWORK_SELECTION_MODE_AUTO); - - Intent intent = new Intent(); - intent.setClassName(SETTINGS_PACKAGE_NAME, - SETTINGS_PACKAGE_NAME + ".Settings$NetworkSelectActivity"); - intent.putExtra(Settings.EXTRA_SUB_ID, mSubId); - preference.setIntent(intent); - } - - @Override - public CharSequence getSummary() { - final ServiceState ss = mTelephonyManager.getServiceState(); - if (ss != null && ss.getState() == ServiceState.STATE_IN_SERVICE) { - return MobileNetworkUtils.getCurrentCarrierNameForDisplay(mContext, mSubId); - } else { - return mContext.getString(R.string.network_disconnected); - } - } - - /** - * Initialization based on given subscription id. - **/ - public OpenNetworkSelectPagePreferenceController init(int subId) { - mSubId = subId; - mTelephonyManager = mContext.getSystemService(TelephonyManager.class) - .createForSubscriptionId(mSubId); - return this; - } - - @Override - public void onNetworkSelectModeUpdated(int mode) { - mCacheOfModeStatus = mode; - if (mPreference != null) { - updateState(mPreference); - } - } -} diff --git a/src/com/android/settings/network/telephony/gsm/OpenNetworkSelectPagePreferenceController.kt b/src/com/android/settings/network/telephony/gsm/OpenNetworkSelectPagePreferenceController.kt new file mode 100644 index 00000000000..1d2b73f3daf --- /dev/null +++ b/src/com/android/settings/network/telephony/gsm/OpenNetworkSelectPagePreferenceController.kt @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2023 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.network.telephony.gsm + +import android.content.Context +import android.content.Intent +import android.provider.Settings +import android.telephony.ServiceState +import android.telephony.TelephonyManager +import androidx.annotation.VisibleForTesting +import androidx.lifecycle.LifecycleOwner +import androidx.preference.Preference +import androidx.preference.PreferenceScreen +import com.android.settings.R +import com.android.settings.Settings.NetworkSelectActivity +import com.android.settings.network.telephony.MobileNetworkUtils +import com.android.settings.network.telephony.TelephonyBasePreferenceController +import com.android.settings.network.telephony.allowedNetworkTypesFlow +import com.android.settings.network.telephony.serviceStateFlow +import com.android.settingslib.spa.framework.util.collectLatestWithLifecycle +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.withContext + +/** + * Preference controller for "Open network select" + */ +class OpenNetworkSelectPagePreferenceController(context: Context, key: String) : + TelephonyBasePreferenceController(context, key), + AutoSelectPreferenceController.OnNetworkSelectModeListener { + + private lateinit var allowedNetworkTypesFlow: Flow + private lateinit var serviceStateFlow: Flow + + private var preference: Preference? = null + + /** + * Initialization based on given subscription id. + */ + fun init(subId: Int): OpenNetworkSelectPagePreferenceController { + mSubId = subId + allowedNetworkTypesFlow = mContext.allowedNetworkTypesFlow(subId) + serviceStateFlow = mContext.serviceStateFlow(subId) + return this + } + + @VisibleForTesting + fun init( + subId: Int, + allowedNetworkTypesFlow: Flow, + serviceStateFlow: Flow, + ) { + mSubId = subId + this.allowedNetworkTypesFlow = allowedNetworkTypesFlow + this.serviceStateFlow = serviceStateFlow + } + + override fun getAvailabilityStatus(subId: Int) = + if (MobileNetworkUtils.shouldDisplayNetworkSelectOptions(mContext, subId)) AVAILABLE + else CONDITIONALLY_UNAVAILABLE + + override fun displayPreference(screen: PreferenceScreen) { + super.displayPreference(screen) + preference = screen.findPreference(preferenceKey) + preference?.intent = Intent().apply { + setClass(mContext, NetworkSelectActivity::class.java) + putExtra(Settings.EXTRA_SUB_ID, mSubId) + } + } + + override fun onViewCreated(viewLifecycleOwner: LifecycleOwner) { + allowedNetworkTypesFlow.collectLatestWithLifecycle(viewLifecycleOwner) { + preference?.isVisible = withContext(Dispatchers.Default) { + MobileNetworkUtils.shouldDisplayNetworkSelectOptions(mContext, mSubId) + } + } + + serviceStateFlow + .collectLatestWithLifecycle(viewLifecycleOwner) { serviceState -> + preference?.summary = if (serviceState.state == ServiceState.STATE_IN_SERVICE) { + withContext(Dispatchers.Default) { + MobileNetworkUtils.getCurrentCarrierNameForDisplay(mContext, mSubId) + } + } else { + mContext.getString(R.string.network_disconnected) + } + } + } + + override fun onNetworkSelectModeUpdated(mode: Int) { + preference?.isEnabled = mode != TelephonyManager.NETWORK_SELECTION_MODE_AUTO + } +} diff --git a/tests/spa_unit/src/com/android/settings/network/telephony/AllowedNetworkTypesFlowTest.kt b/tests/spa_unit/src/com/android/settings/network/telephony/AllowedNetworkTypesFlowTest.kt new file mode 100644 index 00000000000..288018d9090 --- /dev/null +++ b/tests/spa_unit/src/com/android/settings/network/telephony/AllowedNetworkTypesFlowTest.kt @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2023 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.network.telephony + +import android.content.Context +import android.telephony.TelephonyCallback +import android.telephony.TelephonyManager +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.settingslib.spa.testutils.firstWithTimeoutOrNull +import com.android.settingslib.spa.testutils.toListWithTimeout +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.async +import kotlinx.coroutines.delay +import kotlinx.coroutines.runBlocking +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.kotlin.any +import org.mockito.kotlin.doAnswer +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock +import org.mockito.kotlin.spy + +@RunWith(AndroidJUnit4::class) +class AllowedNetworkTypesFlowTest { + + private var allowedNetworkTypesListener: TelephonyCallback.AllowedNetworkTypesListener? = null + + private val mockTelephonyManager = mock { + on { createForSubscriptionId(SUB_ID) } doReturn mock + on { registerTelephonyCallback(any(), any()) } doAnswer { + allowedNetworkTypesListener = + it.arguments[1] as TelephonyCallback.AllowedNetworkTypesListener + } + } + + private val context: Context = spy(ApplicationProvider.getApplicationContext()) { + on { getSystemService(TelephonyManager::class.java) } doReturn mockTelephonyManager + } + + @Test + fun allowedNetworkTypesFlow_initial_notSndInitialValue() = runBlocking { + val flow = context.allowedNetworkTypesFlow(SUB_ID) + + val state = flow.firstWithTimeoutOrNull() + + assertThat(state).isNull() + } + + @Test + fun allowedNetworkTypesFlow_userReasonChanged_sendChanged(): Unit = runBlocking { + val listDeferred = async { + context.allowedNetworkTypesFlow(SUB_ID).toListWithTimeout() + } + delay(100) + + allowedNetworkTypesListener?.onAllowedNetworkTypesChanged( + TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_USER, + ALLOWED_NETWORK_TYPE, + ) + + assertThat(listDeferred.await()).containsExactly(ALLOWED_NETWORK_TYPE) + } + + @Test + fun allowedNetworkTypesFlow_carrierReasonChanged_sendChanged(): Unit = runBlocking { + val listDeferred = async { + context.allowedNetworkTypesFlow(SUB_ID).toListWithTimeout() + } + delay(100) + + allowedNetworkTypesListener?.onAllowedNetworkTypesChanged( + TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_CARRIER, + ALLOWED_NETWORK_TYPE, + ) + + assertThat(listDeferred.await()).containsExactly(ALLOWED_NETWORK_TYPE) + } + + @Test + fun allowedNetworkTypesFlow_powerReasonChanged_notSendChanged() = runBlocking { + val listDeferred = async { + context.allowedNetworkTypesFlow(SUB_ID).toListWithTimeout() + } + delay(100) + + allowedNetworkTypesListener?.onAllowedNetworkTypesChanged( + TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_POWER, + ALLOWED_NETWORK_TYPE, + ) + + assertThat(listDeferred.await()).isEmpty() + } + + private companion object { + const val SUB_ID = 1 + const val ALLOWED_NETWORK_TYPE = 10L + } +} diff --git a/tests/spa_unit/src/com/android/settings/network/telephony/ServiceStateFlowTest.kt b/tests/spa_unit/src/com/android/settings/network/telephony/ServiceStateFlowTest.kt new file mode 100644 index 00000000000..4ffc267aa73 --- /dev/null +++ b/tests/spa_unit/src/com/android/settings/network/telephony/ServiceStateFlowTest.kt @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2023 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.network.telephony + +import android.content.Context +import android.telephony.ServiceState +import android.telephony.TelephonyCallback +import android.telephony.TelephonyManager +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.settingslib.spa.testutils.firstWithTimeoutOrNull +import com.android.settingslib.spa.testutils.toListWithTimeout +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.async +import kotlinx.coroutines.delay +import kotlinx.coroutines.runBlocking +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.kotlin.any +import org.mockito.kotlin.doAnswer +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock +import org.mockito.kotlin.spy + +@RunWith(AndroidJUnit4::class) +class ServiceStateFlowTest { + + private var serviceStateListener: TelephonyCallback.ServiceStateListener? = null + + private val mockTelephonyManager = mock { + on { createForSubscriptionId(SUB_ID) } doReturn mock + on { registerTelephonyCallback(any(), any()) } doAnswer { + serviceStateListener = it.arguments[1] as TelephonyCallback.ServiceStateListener + serviceStateListener?.onServiceStateChanged(ServiceState()) + } + } + + private val context: Context = spy(ApplicationProvider.getApplicationContext()) { + on { getSystemService(TelephonyManager::class.java) } doReturn mockTelephonyManager + } + + @Test + fun serviceStateFlow_initial_sndInitialValue() = runBlocking { + val flow = context.serviceStateFlow(SUB_ID) + + val state = flow.firstWithTimeoutOrNull() + + assertThat(state).isNotNull() + } + + @Test + fun serviceStateFlow_changed_sendChanged(): Unit = runBlocking { + val listDeferred = async { + context.serviceStateFlow(SUB_ID).toListWithTimeout() + } + delay(100) + + serviceStateListener?.onServiceStateChanged(ServiceState()) + + assertThat(listDeferred.await()).hasSize(2) + } + + private companion object { + const val SUB_ID = 1 + } +} diff --git a/tests/spa_unit/src/com/android/settings/network/telephony/TelephonyRepositoryTest.kt b/tests/spa_unit/src/com/android/settings/network/telephony/TelephonyRepositoryTest.kt new file mode 100644 index 00000000000..b7e1dcc37b5 --- /dev/null +++ b/tests/spa_unit/src/com/android/settings/network/telephony/TelephonyRepositoryTest.kt @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2023 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.network.telephony + +import android.content.Context +import android.telephony.TelephonyCallback +import android.telephony.TelephonyManager +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.settingslib.spa.testutils.firstWithTimeoutOrNull +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.runBlocking +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.kotlin.any +import org.mockito.kotlin.doAnswer +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock +import org.mockito.kotlin.spy +import org.mockito.kotlin.verify + +@RunWith(AndroidJUnit4::class) +class TelephonyRepositoryTest { + private var telephonyCallback: TelephonyCallback? = null + + private val mockTelephonyManager = mock { + on { createForSubscriptionId(SUB_ID) } doReturn mock + on { registerTelephonyCallback(any(), any()) } doAnswer { + telephonyCallback = it.arguments[1] as TelephonyCallback + } + } + + private val context: Context = spy(ApplicationProvider.getApplicationContext()) { + on { getSystemService(TelephonyManager::class.java) } doReturn mockTelephonyManager + } + + @Test + fun telephonyCallbackFlow_callbackRegistered() = runBlocking { + val flow = context.telephonyCallbackFlow(SUB_ID) { + object : TelephonyCallback() {} + } + + flow.firstWithTimeoutOrNull() + + assertThat(telephonyCallback).isNotNull() + } + + @Test + fun telephonyCallbackFlow_callbackUnregistered() = runBlocking { + val flow = context.telephonyCallbackFlow(SUB_ID) { + object : TelephonyCallback() {} + } + + flow.firstWithTimeoutOrNull() + + verify(mockTelephonyManager).unregisterTelephonyCallback(telephonyCallback!!) + } + + private companion object { + const val SUB_ID = 1 + } +} diff --git a/tests/spa_unit/src/com/android/settings/network/telephony/gsm/OpenNetworkSelectPagePreferenceControllerTest.kt b/tests/spa_unit/src/com/android/settings/network/telephony/gsm/OpenNetworkSelectPagePreferenceControllerTest.kt new file mode 100644 index 00000000000..b749a3afff0 --- /dev/null +++ b/tests/spa_unit/src/com/android/settings/network/telephony/gsm/OpenNetworkSelectPagePreferenceControllerTest.kt @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2023 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.network.telephony.gsm + +import android.content.Context +import android.telephony.ServiceState +import android.telephony.SubscriptionInfo +import android.telephony.SubscriptionManager +import android.telephony.TelephonyManager +import androidx.lifecycle.testing.TestLifecycleOwner +import androidx.preference.Preference +import androidx.preference.PreferenceManager +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.settings.R +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.emptyFlow +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.runBlocking +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.kotlin.doAnswer +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock +import org.mockito.kotlin.spy + +@RunWith(AndroidJUnit4::class) +class OpenNetworkSelectPagePreferenceControllerTest { + + private val subscriptionInfo = mock { + on { subscriptionId } doReturn SUB_ID + on { carrierName } doReturn OPERATOR_NAME + } + + private val mockSubscriptionManager = mock { + on { activeSubscriptionInfoList } doAnswer { listOf(subscriptionInfo) } + } + + private val context: Context = spy(ApplicationProvider.getApplicationContext()) { + on { getSystemService(SubscriptionManager::class.java) } doReturn mockSubscriptionManager + } + + private val preference = Preference(context).apply { key = TEST_KEY } + private val preferenceScreen = PreferenceManager(context).createPreferenceScreen(context) + + private val controller = OpenNetworkSelectPagePreferenceController(context, TEST_KEY) + + private val serviceState = ServiceState() + + @Before + fun setUp() { + preferenceScreen.addPreference(preference) + controller.apply { + init( + subId = SUB_ID, + allowedNetworkTypesFlow = emptyFlow(), + serviceStateFlow = flowOf(serviceState), + ) + displayPreference(preferenceScreen) + } + } + + @Test + fun isEnabled_modeManual_enabled() { + controller.onNetworkSelectModeUpdated(TelephonyManager.NETWORK_SELECTION_MODE_MANUAL) + + assertThat(preference.isEnabled).isTrue() + } + + @Test + fun isEnabled_modeAuto_disabled() { + controller.onNetworkSelectModeUpdated(TelephonyManager.NETWORK_SELECTION_MODE_AUTO) + + assertThat(preference.isEnabled).isFalse() + } + + @Test + fun summary_inService_isOperatorName() = runBlocking { + serviceState.state = ServiceState.STATE_IN_SERVICE + + controller.onViewCreated(TestLifecycleOwner()) + delay(100) + + assertThat(preference.summary).isEqualTo(OPERATOR_NAME) + } + + @Test + fun summary_notInService_isDisconnect() = runBlocking { + serviceState.state = ServiceState.STATE_OUT_OF_SERVICE + + controller.onViewCreated(TestLifecycleOwner()) + delay(100) + + assertThat(preference.summary).isEqualTo(context.getString(R.string.network_disconnected)) + } + + private companion object { + const val TEST_KEY = "test_key" + const val SUB_ID = 2 + const val OPERATOR_NAME = "T-mobile" + } +} diff --git a/tests/unit/src/com/android/settings/network/telephony/gsm/OpenNetworkSelectPagePreferenceControllerTest.java b/tests/unit/src/com/android/settings/network/telephony/gsm/OpenNetworkSelectPagePreferenceControllerTest.java deleted file mode 100644 index b4d49b2a838..00000000000 --- a/tests/unit/src/com/android/settings/network/telephony/gsm/OpenNetworkSelectPagePreferenceControllerTest.java +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright (C) 2020 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.network.telephony.gsm; - -import static com.google.common.truth.Truth.assertThat; - -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.when; - -import android.content.Context; -import android.os.PersistableBundle; -import android.telephony.CarrierConfigManager; -import android.telephony.ServiceState; -import android.telephony.SubscriptionInfo; -import android.telephony.SubscriptionManager; -import android.telephony.TelephonyManager; - -import androidx.preference.Preference; -import androidx.test.core.app.ApplicationProvider; -import androidx.test.ext.junit.runners.AndroidJUnit4; - -import com.android.settings.testutils.ResourcesUtils; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -import java.util.Arrays; - -@RunWith(AndroidJUnit4.class) -public class OpenNetworkSelectPagePreferenceControllerTest { - private static final int SUB_ID = 2; - private static final String OPERATOR_NAME = "T-mobile"; - - @Mock - private TelephonyManager mTelephonyManager; - @Mock - private SubscriptionManager mSubscriptionManager; - @Mock - private CarrierConfigManager mCarrierConfigManager; - @Mock - private ServiceState mServiceState; - @Mock - private SubscriptionInfo mSubscriptionInfo; - - private PersistableBundle mCarrierConfig; - private OpenNetworkSelectPagePreferenceController mController; - private Preference mPreference; - private Context mContext; - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - - mContext = spy(ApplicationProvider.getApplicationContext()); - - when(mContext.getSystemService(TelephonyManager.class)).thenReturn(mTelephonyManager); - when(mContext.getSystemService(SubscriptionManager.class)).thenReturn(mSubscriptionManager); - when(mContext.getSystemService(CarrierConfigManager.class)).thenReturn( - mCarrierConfigManager); - when(mTelephonyManager.createForSubscriptionId(SUB_ID)).thenReturn(mTelephonyManager); - when(mTelephonyManager.getServiceState()).thenReturn(mServiceState); - - mCarrierConfig = new PersistableBundle(); - when(mCarrierConfigManager.getConfigForSubId(SUB_ID)).thenReturn(mCarrierConfig); - - when(mSubscriptionInfo.getSubscriptionId()).thenReturn(SUB_ID); - when(mSubscriptionInfo.getCarrierName()).thenReturn(OPERATOR_NAME); - - when(mSubscriptionManager.getActiveSubscriptionInfoList()).thenReturn( - Arrays.asList(mSubscriptionInfo)); - - when(mTelephonyManager.getNetworkOperatorName()).thenReturn(OPERATOR_NAME); - - mPreference = new Preference(mContext); - mController = new OpenNetworkSelectPagePreferenceController(mContext, - "open_network_select") { - @Override - public void updateState(Preference preference) { - super.updateState(mPreference); - } - }; - mController.init(SUB_ID); - } - - @Test - public void updateState_modeAuto_disabled() { - mController.onNetworkSelectModeUpdated(TelephonyManager.NETWORK_SELECTION_MODE_AUTO); - mController.updateState(mPreference); - - assertThat(mPreference.isEnabled()).isFalse(); - } - - @Test - public void getSummary_inService_returnOperatorName() { - when(mServiceState.getState()).thenReturn(ServiceState.STATE_IN_SERVICE); - - assertThat(mController.getSummary()).isEqualTo(OPERATOR_NAME); - } - - @Test - public void getSummary_notInService_returnDisconnect() { - when(mServiceState.getState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE); - - assertThat(mController.getSummary()).isEqualTo( - ResourcesUtils.getResourcesString(mContext, "network_disconnected")); - } -}