Hide MMS messages if switch mobile data automatically

Per designer's comment, hide the "MMS messages" if user cannot use it to
control MMS.

Fix: 303759893
Test: manual - turn "Switch mobile data automatically" on / off
Test: unit test
Change-Id: I874524c0cedb48f7daf87f32920e26428ae78c89
This commit is contained in:
Chaohui Wang
2023-12-15 12:10:31 +08:00
parent facd2f7197
commit 007ef134b6
5 changed files with 150 additions and 47 deletions

View File

@@ -17,37 +17,22 @@
package com.android.settings.network package com.android.settings.network
import android.app.Application import android.app.Application
import android.telephony.SubscriptionInfo
import android.telephony.SubscriptionManager import android.telephony.SubscriptionManager
import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.android.settings.network.telephony.subscriptionsChangedFlow
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.asExecutor
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.conflate
import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.plus import kotlinx.coroutines.plus
class SubscriptionInfoListViewModel(application: Application) : AndroidViewModel(application) { class SubscriptionInfoListViewModel(application: Application) : AndroidViewModel(application) {
private val subscriptionManager =
application.getSystemService(SubscriptionManager::class.java)!!
private val scope = viewModelScope + Dispatchers.Default private val scope = viewModelScope + Dispatchers.Default
val subscriptionInfoListFlow = callbackFlow<List<SubscriptionInfo>> {
val subscriptionManager = application.getSystemService(SubscriptionManager::class.java)!!
val listener = object : SubscriptionManager.OnSubscriptionsChangedListener() { val subscriptionInfoListFlow = application.subscriptionsChangedFlow().map {
override fun onSubscriptionsChanged() { SubscriptionUtil.getActiveSubscriptions(subscriptionManager)
trySend(SubscriptionUtil.getActiveSubscriptions(subscriptionManager)) }.stateIn(scope, SharingStarted.Eagerly, initialValue = emptyList())
}
}
subscriptionManager.addOnSubscriptionsChangedListener(
Dispatchers.Default.asExecutor(),
listener,
)
awaitClose { subscriptionManager.removeOnSubscriptionsChangedListener(listener) }
}.conflate().stateIn(scope, SharingStarted.Eagerly, initialValue = emptyList())
} }

View File

@@ -17,33 +17,25 @@
package com.android.settings.network.telephony package com.android.settings.network.telephony
import android.content.Context import android.content.Context
import android.os.Handler
import android.os.Looper
import android.telephony.SubscriptionManager import android.telephony.SubscriptionManager
import android.telephony.TelephonyManager import android.telephony.TelephonyManager
import android.telephony.data.ApnSetting import android.telephony.data.ApnSetting
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import androidx.preference.PreferenceScreen import androidx.preference.PreferenceScreen
import com.android.settings.network.MobileDataContentObserver import com.android.settings.network.mobileDataEnabledFlow
import com.android.settingslib.spa.framework.util.collectLatestWithLifecycle
import kotlinx.coroutines.flow.combine
/** /**
* Preference controller for "MMS messages" * Preference controller for "MMS messages"
*/ */
class MmsMessagePreferenceController(context: Context, key: String) : class MmsMessagePreferenceController(context: Context, key: String) :
TelephonyTogglePreferenceController(context, key), DefaultLifecycleObserver { TelephonyTogglePreferenceController(context, key) {
private lateinit var telephonyManager: TelephonyManager private lateinit var telephonyManager: TelephonyManager
private var preferenceScreen: PreferenceScreen? = null private var preferenceScreen: PreferenceScreen? = null
private val mobileDataContentObserver =
MobileDataContentObserver(Handler(Looper.getMainLooper())).apply {
setOnMobileDataChangedListener {
preferenceScreen?.let { super.displayPreference(it) }
}
}
fun init(subId: Int) { fun init(subId: Int) {
mSubId = subId mSubId = subId
telephonyManager = mContext.getSystemService(TelephonyManager::class.java)!! telephonyManager = mContext.getSystemService(TelephonyManager::class.java)!!
@@ -53,26 +45,25 @@ class MmsMessagePreferenceController(context: Context, key: String) :
override fun getAvailabilityStatus(subId: Int) = override fun getAvailabilityStatus(subId: Int) =
if (subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID && if (subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID &&
!telephonyManager.isDataEnabled && !telephonyManager.isDataEnabled &&
telephonyManager.isApnMetered(ApnSetting.TYPE_MMS) telephonyManager.isApnMetered(ApnSetting.TYPE_MMS) &&
!telephonyManager.isMobileDataPolicyEnabled(
TelephonyManager.MOBILE_DATA_POLICY_AUTO_DATA_SWITCH
)
) AVAILABLE else CONDITIONALLY_UNAVAILABLE ) AVAILABLE else CONDITIONALLY_UNAVAILABLE
override fun onStart(owner: LifecycleOwner) {
if (mSubId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
mobileDataContentObserver.register(mContext, mSubId)
}
}
override fun onStop(owner: LifecycleOwner) {
if (mSubId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
mobileDataContentObserver.unRegister(mContext)
}
}
override fun displayPreference(screen: PreferenceScreen) { override fun displayPreference(screen: PreferenceScreen) {
super.displayPreference(screen) super.displayPreference(screen)
preferenceScreen = screen preferenceScreen = screen
} }
override fun onViewCreated(viewLifecycleOwner: LifecycleOwner) {
combine(
mContext.mobileDataEnabledFlow(mSubId),
mContext.subscriptionsChangedFlow(), // Capture isMobileDataPolicyEnabled() changes
) { _, _ -> }.collectLatestWithLifecycle(viewLifecycleOwner) {
preferenceScreen?.let { super.displayPreference(it) } }
}
override fun isChecked(): Boolean = telephonyManager.isMobileDataPolicyEnabled( override fun isChecked(): Boolean = telephonyManager.isMobileDataPolicyEnabled(
TelephonyManager.MOBILE_DATA_POLICY_MMS_ALWAYS_ALLOWED TelephonyManager.MOBILE_DATA_POLICY_MMS_ALWAYS_ALLOWED
) )

View File

@@ -0,0 +1,43 @@
/*
* 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.SubscriptionManager
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.asExecutor
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.conflate
import kotlinx.coroutines.flow.flowOn
fun Context.subscriptionsChangedFlow() = callbackFlow {
val subscriptionManager = getSystemService(SubscriptionManager::class.java)!!
val listener = object : SubscriptionManager.OnSubscriptionsChangedListener() {
override fun onSubscriptionsChanged() {
trySend(Unit)
}
}
subscriptionManager.addOnSubscriptionsChangedListener(
Dispatchers.Default.asExecutor(),
listener,
)
awaitClose { subscriptionManager.removeOnSubscriptionsChangedListener(listener) }
}.conflate().flowOn(Dispatchers.Default)

View File

@@ -79,6 +79,20 @@ class MmsMessagePreferenceControllerTest {
assertThat(availabilityStatus).isEqualTo(CONDITIONALLY_UNAVAILABLE) assertThat(availabilityStatus).isEqualTo(CONDITIONALLY_UNAVAILABLE)
} }
@Test
fun getAvailabilityStatus_autoDataSwitch_returnUnavailable() {
mockTelephonyManager.stub {
on { isApnMetered(ApnSetting.TYPE_MMS) } doReturn true
on {
isMobileDataPolicyEnabled(TelephonyManager.MOBILE_DATA_POLICY_AUTO_DATA_SWITCH)
} doReturn true
}
val availabilityStatus = controller.getAvailabilityStatus(SUB_ID)
assertThat(availabilityStatus).isEqualTo(CONDITIONALLY_UNAVAILABLE)
}
@Test @Test
fun getAvailabilityStatus_mobileDataOffWithValidSubId_returnAvailable() { fun getAvailabilityStatus_mobileDataOffWithValidSubId_returnAvailable() {
mockTelephonyManager.stub { mockTelephonyManager.stub {

View File

@@ -0,0 +1,70 @@
/*
* 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.SubscriptionManager
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 SubscriptionRepositoryTest {
private var subInfoListener: SubscriptionManager.OnSubscriptionsChangedListener? = null
private val mockSubscriptionManager = mock<SubscriptionManager> {
on { addOnSubscriptionsChangedListener(any(), any()) } doAnswer {
subInfoListener = it.arguments[1] as SubscriptionManager.OnSubscriptionsChangedListener
subInfoListener?.onSubscriptionsChanged()
}
}
private val context: Context = spy(ApplicationProvider.getApplicationContext()) {
on { getSystemService(SubscriptionManager::class.java) } doReturn mockSubscriptionManager
}
@Test
fun subscriptionsChangedFlow_hasInitialValue() = runBlocking {
val initialValue = context.subscriptionsChangedFlow().firstWithTimeoutOrNull()
assertThat(initialValue).isSameInstanceAs(Unit)
}
@Test
fun subscriptionsChangedFlow_changed() = runBlocking {
val listDeferred = async {
context.subscriptionsChangedFlow().toListWithTimeout()
}
delay(100)
subInfoListener?.onSubscriptionsChanged()
assertThat(listDeferred.await()).hasSize(2)
}
}