Android Mobile Network Settings | APN 菜单加载异常
问题
从log看是有创建APN对应的Controller(功能逻辑是ok的),但是Mobile Network Settings无法显示(UI异常)。
日志分析
看似APN 菜单已经创建了,实际上并没有显示。
11-12 07:01:28.150 8773 8773 D PrefCtrlListHelper: Could not find Context-only controller for pref: com.android.settings.network.telephony.ApnPreferenceController
11-12 07:01:28.164 8773 8773 D ApnPreferenceController: init: subId = 1
Debug 1:表面原因 isGsmApn
debug打印log的时候会发现,使用平板时preference是不可见的,无法确认是不是getAvailabilityStatus影响了displayPreference。
- Tablet Log:
11-13 17:10:46.692 7796 9288 D ApnPreferenceController: getAvailabilityStatus hideCarrierNetwork = false, isCdmaApn = false, isGsmApn = false
11-13 17:10:46.705 7796 7796 I ApnPreferenceController: displayPreference: isShow = false, isVisible = false, isEnable = true
- Phone Log:
11-13 03:25:47.285 13222 13222 D ApnPreferenceController: init: subId = 3
11-13 03:25:47.323 13222 13401 D ApnPreferenceController: getAvailabilityStatus hideCarrierNetwork = false, isCdmaApn = false, isGsmApn = true
11-13 03:25:47.372 13222 13222 I ApnPreferenceController: displayPreference: isShow = true, isVisible = true, isEnable = true
Debug 2:根因 isGsmOptions
- Phone Log:
11-13 05:14:30.305 15022 15022 D SatelliteSettingPreferenceController: init(), subId=3
11-13 05:14:30.305 15022 15022 D ApnPreferenceController: init: subId = 311-13 05:14:30.354 15022 15233 D ApnPreferenceController: isGsmOption = true
11-13 05:14:30.354 15022 15233 D ApnPreferenceController: KEY_APN_EXPAND_BOOL = true11-13 05:14:30.355 15022 15233 D ApnPreferenceController: getAvailabilityStatus hideCarrierNetwork = false, isCdmaApn = false, isGsmApn = true
11-13 05:14:30.448 15022 15022 I ApnPreferenceController: displayPreference: isShow = true, isVisible = true, isEnable = true
11-13 05:14:31.218 15022 15022 D ApnPreferenceController: updateState: preferenceKey = telephony_apn_key
11-13 05:14:32.350 15022 15022 D ApnPreferenceController: handlePreferenceTreeClick
- Tablet Log:
11-13 18:13:24.359 11793 11793 D ApnPreferenceController: isGsmOption = false
11-13 18:13:24.359 11793 11793 D ApnPreferenceController: KEY_APN_EXPAND_BOOL = true
11-13 18:13:24.359 11793 11793 D ApnPreferenceController: getAvailabilityStatus hideCarrierNetwork = false, isCdmaApn = false, isGsmApn = false
11-13 18:13:24.360 11793 11793 I ApnPreferenceController: displayPreference: isShow = false, isVisible = false, isEnable = true
代码解读
移动网络界面加入APN的菜单
mobile_network_settings.xml
先来看看界面设计逻辑
<!-- Copyright (C) 2019 The Android Open Source Project -->
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:settings="http://schemas.android.com/apk/res-auto"
android:key="mobile_network_pref_screen">
<com.android.settings.spa.preference.ComposePreference
android:key="use_sim_switch"
settings:controller="com.android.settings.network.telephony.MobileNetworkSwitchController"/>
<!-- 省略移动网络其他controller -->
<!--We want separate APN setting from reset of settings because we want user to change it with caution-->
<com.android.settingslib.RestrictedPreference
android:key="telephony_apn_key"
android:persistent="false"
android:title="@string/mobile_network_apn_title"
settings:keywords="@string/keywords_access_point_names"
settings:controller="com.android.settings.network.telephony.ApnPreferenceController"/>
</PreferenceScreen>
标题定义
packages\apps\Settings\res\values\strings.xml
<!-- Title for Apn settings in mobile network settings [CHAR LIMIT=60] -->
<string name="mobile_network_apn_title">Access Point Names</string>
ApnPreferenceController
@Override
public int getAvailabilityStatus(int subId) {
final PersistableBundle carrierConfig = mCarrierConfigCache.getConfigForSubId(subId);
final boolean isCdmaApn = MobileNetworkUtils.isCdmaOptions(mContext, subId)
&& carrierConfig != null
&& carrierConfig.getBoolean(CarrierConfigManager.KEY_SHOW_APN_SETTING_CDMA_BOOL);
final boolean isGsmApn = MobileNetworkUtils.isGsmOptions(mContext, subId)
&& carrierConfig != null
&& carrierConfig.getBoolean(CarrierConfigManager.KEY_APN_EXPAND_BOOL);
final boolean hideCarrierNetwork = carrierConfig == null
|| carrierConfig.getBoolean(
CarrierConfigManager.KEY_HIDE_CARRIER_NETWORK_SETTINGS_BOOL);
return !hideCarrierNetwork && (isCdmaApn || isGsmApn)
? AVAILABLE
: CONDITIONALLY_UNAVAILABLE;
}
@Override
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
Log.i(TAG, "displayPreference + ");
mPreference = screen.findPreference(getPreferenceKey());
//For debug as below
if (mPreference != null) {
Log.i(TAG, "displayPreference: isShow = " + mPreference.isShown() +
", isVisible = " + mPreference.isVisible() + ", isEnable = " + mPreference.isEnabled());
}
mPreference.setEnabled(true); //是否置灰
mPreference.setVisible(true); //是否显示
Log.i(TAG, "displayPreference: enable and visible.");
}
继续分析preference的逻辑,沿着 getAvailabilityStatus 可以看到,在 ApnPreferenceController 其父类实现的接口 TelephonyAvailabilityCallback 中会回调
packages/apps/Settings/src/com/android/settings/network/telephony/ApnPreferenceController.java
**
* Preference controller for "Apn settings"
*/
public class ApnPreferenceController extends TelephonyBasePreferenceController implements
LifecycleObserver, OnStart, OnStop {}
父类 TelephonyBasePreferenceController
packages/apps/Settings/src/com/android/settings/network/telephony/TelephonyBasePreferenceController.java
/**
* {@link BasePreferenceController} that used by all preferences that requires subscription id.
*/
public abstract class TelephonyBasePreferenceController extends BasePreferenceController
implements TelephonyAvailabilityCallback, TelephonyAvailabilityHandler {}
可用性接口TelephonyAvailabilityCallback
packages/apps/Settings/src/com/android/settings/network/telephony/TelephonyAvailabilityCallback.java
package com.android.settings.network.telephony;
/**
* Callback to decide whether preference is available based on subscription id
*/
public interface TelephonyAvailabilityCallback {
/**
* Return availability status for a specific subId
*
* @see TelephonyBasePreferenceController
* @see TelephonyTogglePreferenceController
*/
int getAvailabilityStatus(int subId);
}
MobileNetworkUtils
packages/apps/Settings/src/com/android/settings/network/telephony/MobileNetworkUtils.java
/**
* Return availability for a default subscription id. If subId already been set, use it to
* check, otherwise traverse all active subIds on device to check.
* @param context context
* @param defSubId Default subId get from telephony preference controller
* @param callback Callback to check availability for a specific subId
* @return Availability
*
* @see BasePreferenceController#getAvailabilityStatus()
*/
public static int getAvailability(Context context, int defSubId,
TelephonyAvailabilityCallback callback) {
if (defSubId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
// If subId has been set, return the corresponding status
return callback.getAvailabilityStatus(defSubId);
} else {
// Otherwise, search whether there is one subId in device that support this preference
final int[] subIds = getActiveSubscriptionIdList(context);
if (ArrayUtils.isEmpty(subIds)) {
return callback.getAvailabilityStatus(
SubscriptionManager.INVALID_SUBSCRIPTION_ID);
} else {
for (final int subId : subIds) {
final int status = callback.getAvailabilityStatus(subId);
if (status == BasePreferenceController.AVAILABLE) {
return status;
}
}
return callback.getAvailabilityStatus(subIds[0]);
}
}
}
/**
* return {@code true} if we need show Gsm related settings
*/
public static boolean isGsmOptions(Context context, int subId) {
if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
return false;
}
if (isGsmBasicOptions(context, subId)) {
return true;
}
final TelephonyManager telephonyManager = context.getSystemService(TelephonyManager.class)
.createForSubscriptionId(subId);
final int networkMode = getNetworkTypeFromRaf(
(int) telephonyManager.getAllowedNetworkTypesForReason(
TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_USER));
if (isWorldMode(context, subId)) {
if (networkMode == NETWORK_MODE_LTE_CDMA_EVDO
|| networkMode == NETWORK_MODE_LTE_GSM_WCDMA
|| networkMode == NETWORK_MODE_NR_LTE_CDMA_EVDO
|| networkMode == NETWORK_MODE_NR_LTE_GSM_WCDMA) {
return true;
} else if (shouldSpeciallyUpdateGsmCdma(context, subId)) {
return true;
}
}
return false;
}
private static boolean isGsmBasicOptions(Context context, int subId) {
final PersistableBundle carrierConfig =
CarrierConfigCache.getInstance(context).getConfigForSubId(subId);
if (carrierConfig != null
&& !carrierConfig.getBoolean(
CarrierConfigManager.KEY_HIDE_CARRIER_NETWORK_SETTINGS_BOOL)
&& carrierConfig.getBoolean(CarrierConfigManager.KEY_WORLD_PHONE_BOOL)) {
return true;
}
final TelephonyManager telephonyManager = context.getSystemService(TelephonyManager.class)
.createForSubscriptionId(subId);
if (telephonyManager.getPhoneType() == TelephonyManager.PHONE_TYPE_GSM) {
return true;
}
return false;
}
}
解决方案
如何更改平板上面gsm的判断?