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。

isGsmOptions
MobileNetworkUtils - isGsmOptions

手机设备能满足如下的phoneType

isGsmBasicOptions
MobileNetworkUtils - isGsmBasicOptions
  • 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 -->

<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

  • KEY_SHOW_APN_SETTING_CDMA_BOOL
  • KEY_APN_EXPAND_BOOL
  • KEY_HIDE_CARRIER_NETWORK_SETTINGS_BOOL
    @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

  • 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 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;
    }


}

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

### APN Server Configuration and Information In the context of mobile networks, an Access Point Name (APN) is used to establish a connection between a device on a cellular network and an external network such as the Internet or a private corporate network. The configuration details for an APN server are critical for setting up this connectivity properly. The **Subscription Data AVP** within ULA messages contains important configurations including the **APN Configuration Profile AVP**, which further includes multiple **APN Configuration AVPs**[^1]. Each individual **APN Configuration AVP** holds specific attributes about one APN that can be subscribed by users, like: - `APN-NI`: Network Identifier part of the APN name. - `PDN Type`: Packet data network type indicating whether it supports IPv4, IPv6, or both. - `APN-AMBR`: Aggregated Maximum Bit Rate defining maximum bandwidth limits for non-guaranteed bit rate bearers. For configuring servers associated with these settings, typically system administrators would need access to operator-provided documentation detailing how their particular setup should handle each parameter mentioned above. Additionally, installing necessary modules via package managers might also form part of preparatory steps when dealing specifically with Apple Push Notification service connections using Node.js environments[^2]. However, direct interaction with APN servers often requires specialized software provided by telecom operators rather than generic libraries available through public repositories unless working explicitly within development frameworks supported directly by those providers. ```bash npm install apn ``` This command installs a library useful primarily for interacting with Apple's push notification services but not directly relevant to configuring traditional mobile network APNs. --related questions-- 1. How does the PDN Type influence the choice of IP address allocation during PDU session establishment? 2. What role do AVPs play in Diameter protocol communications over S6a interfaces? 3. Can you explain more about the relationship between APN Configuration Profiles and user subscriptions? 4. In what scenarios would adjusting the APN-AMBR parameters significantly impact performance? 5. Are there any security considerations unique to configuring APN-related components?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值