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
This commit is contained in:
Chaohui Wang
2023-12-19 18:10:12 +08:00
parent 72d638e681
commit 28b85b5810
13 changed files with 622 additions and 281 deletions

View File

@@ -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";

View File

@@ -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<ServiceState> {
private static final String TAG = "ServiceStateStatus";

View File

@@ -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<Long> = 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")
}
}
}
}

View File

@@ -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<Int> = callbackFlow {
val telephonyManager = getSystemService(TelephonyManager::class.java)!!
.createForSubscriptionId(subId)
val callback = object : TelephonyCallback(), TelephonyCallback.CallStateListener {
fun Context.callStateFlow(subId: Int): Flow<Int> = 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)
}

View File

@@ -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<ServiceState> = telephonyCallbackFlow(subId) {
object : TelephonyCallback(), TelephonyCallback.ServiceStateListener {
override fun onServiceStateChanged(serviceState: ServiceState) {
trySend(serviceState)
Log.d(TAG, "[$subId] serviceState: $serviceState")
}
}
}

View File

@@ -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 <T> Context.telephonyCallbackFlow(
subId: Int,
block: ProducerScope<T>.() -> TelephonyCallback,
): Flow<T> = 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)

View File

@@ -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);
}
}
}

View File

@@ -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<Long>
private lateinit var serviceStateFlow: Flow<ServiceState>
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<Long>,
serviceStateFlow: Flow<ServiceState>,
) {
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
}
}