当前位置: 首页 > news >正文

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 = 3

11-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 = true

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


http://www.mrgr.cn/news/74991.html

相关文章:

  • Docker 命令总结:从入门到入土
  • Spring Cloud Eureka 服务注册与发现
  • JAVA学习日记(十五) 数据结构
  • Conpair: 配对样本一致性concordance与污染contamination分析
  • 点云论文阅读-1-pointnet++
  • jmeter基础05_第1个http请求
  • 解密复杂系统:理论、模型与案例(3)
  • 计算机网络(7)
  • 山泽光纤HDMI线:铜线的隐藏力量
  • 《人类简史:从动物到上帝》读书笔记
  • Redhat7.9 安装 KingbaseES 金仓数据库 V9单机版(静默安装)
  • NFC批量写入网址、文本、应用app、蓝牙
  • 该如何升级Tableau server呢?
  • FastHTML快速入门: Cookies,Sessions,提示,认证和授权
  • 人机界面与人们常说的“触摸屏”有什么区别?这下终于清楚了
  • 谷歌浏览器扩展程序开发指南
  • Linux项目自动化构建工具—make与makefile
  • spring @Qualifier 注解解决依赖注入时类型相同但名称不同的 bean问题
  • window上 opencpn主要文件位置
  • JavaScript——DOM编程、JS的对象和JSON
  • VTK知识学习(8)-坐标系统
  • 18 为什么这些SQL语句逻辑相同,性能却差异巨大?
  • Spring Data Redis常见操作总结
  • Redis使用
  • 初识Redis
  • 护照阅读器在银行应用:提升客户身份认证效率,强化金融安全防护