Android Mobile Network Settings | APN 菜单加载异常
问题
从log看是有创建APN对应的Controller(功能逻辑是ok的),但是Mobile Network Settings无法显示(UI异常)。
相关术语:
- GSM(Global System for Mobile Communications) 全球移动通信系统,GSM 使用时分多址(TDMA)技术
- CDMA (Code Division Multiple Access)码分多址
日志分析
看似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
在手机设备上,会判断为isGsmBasicOptions,直接返回true,而平板设备的isGsmBasicOptions以及isWorldMode都是false,没有任何一个类型适合,isGsmOptions直接返回了false
- 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 --><PreferenceScreenxmlns: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.ComposePreferenceandroid: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.RestrictedPreferenceandroid: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
- KEY_SHOW_APN_SETTING_CDMA_BOOL
- KEY_APN_EXPAND_BOOL
- KEY_HIDE_CARRIER_NETWORK_SETTINGS_BOOL
@Overridepublic 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;}@Overridepublic void displayPreference(PreferenceScreen screen) {super.displayPreference(screen);Log.i(TAG, "displayPreference + ");mPreference = screen.findPreference(getPreferenceKey());//For debug as belowif (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 implementsLifecycleObserver, 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 BasePreferenceControllerimplements 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
- isGsmBasicOptions 以下默认都是false,不管是手机还是平板
- hide_carrier_network_settings_bool
- world_phone_bool
所以关键是确定设备是否支持GSM网络
final TelephonyManager telephonyManager = context.getSystemService(TelephonyManager.class).createForSubscriptionId(subId); if (telephonyManager.getPhoneType() == TelephonyManager.PHONE_TYPE_GSM) {Log.d(TAG, "isGsmBasicOptions: PHONE_TYPE_GSM");return true; }
/*** 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 statusreturn callback.getAvailabilityStatus(defSubId);} else {// Otherwise, search whether there is one subId in device that support this preferencefinal 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;}}
Settings Manifest文件
在配置文件中绑定title,就不用再其他layout中定义
<activity android:name="Settings$ApnSettingsActivity"android:label="@string/apn_settings"android:exported="true"android:configChanges="orientation|keyboardHidden|screenSize"><intent-filter android:priority="1"><action android:name="android.settings.APN_SETTINGS" /><category android:name="android.intent.category.DEFAULT" /></intent-filter><intent-filter><action android:name="android.intent.action.MAIN" /><category android:name="android.intent.category.DEFAULT" /><category android:name="android.intent.category.VOICE_LAUNCH" /></intent-filter><meta-data android:name="com.android.settings.PRIMARY_PROFILE_CONTROLLED"android:value="true" /><meta-data android:name="com.android.settings.FRAGMENT_CLASS"android:value="com.android.settings.network.apn.ApnSettings" /><meta-data android:name="com.android.settings.HIGHLIGHT_MENU_KEY"android:value="@string/menu_key_network"/></activity>
其他:
GsmUmtsOptions
解决方案
如何更改平板上面gsm的判断?
在Android 早期版本的mobilenetwork是没有使用controller管理preference的。
- mobile_network_settings.xml
移除isGsmApn判断可以显示preference
final boolean isGsmApn = MobileNetworkUtils.isGsmOptions(mContext, subId)&& carrierConfig != null&& carrierConfig.getBoolean(CarrierConfigManager.KEY_APN_EXPAND_BOOL);
因为carrierconfig配置是默认true的,平板也是
public static final String KEY_APN_EXPAND_BOOL = "apn_expand_bool";