【笔记】Android Settings 应用设置菜单的界面代码介绍

简介

Settings应用中,提供多类设置菜单入口,每个菜单内又有各模块功能的实现。

那么各个模块基于Settings 基础的界面Fragment去实现UI,层层按不同业务进行封装继承实现子类:

  • DashboardFragment
  • SettingsPreferenceFragment

功能设置页中的菜单又是通过Controller去实现业务并进行UI动态更新控制。

代码

基于 Android U 平台的实现进行介绍。

公用基类

DashboardFragment

packages/apps/Settings/src/com/android/settings/dashboard/DashboardFragment.java

java 复制代码
package com.android.settings.dashboard;

/**
 * Base fragment for dashboard style UI containing a list of static and dynamic setting items.
 */
public abstract class DashboardFragment extends SettingsPreferenceFragment
        implements CategoryListener, Indexable, PreferenceGroup.OnExpandButtonClickListener,
        BasePreferenceController.UiBlockListener {
    public static final String CATEGORY = "category";
    private static final String TAG = "DashboardFragment";
    private static final long TIMEOUT_MILLIS = 50L;

    /**
     * Get a list of {@link AbstractPreferenceController} for this fragment.
     */
    protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
        return null;
    }
复制代码

模块案例

继承关系:(自上而下,底层是父类,除了MobileNetwork,其他都是抽象类,直到Fragment)

  • MobileNetworkSettings --- 移动网络设置
  • AbstractMobileNetworkSettings
  • RestrictedDashboardFragment
  • DashboardFragment
  • SettingsPreferenceFragment
  • InstrumentedPreferenceFragment - - /com/android/settings/core/
  • ObservablePreferenceFragment -- /frameworks/base/packages/SettingsLib
  • PreferenceFragmentCompat-- classes.jar/androidx.preference
  • Fragment

AbstractMobileNetworkSettings是一个抽象类,用于提供移动网络设置相关的基本功能和行为。

RestrictedDashboardFragment是在DashboardFragment的基础上添加了一些限制或额外的安全措施,以限制用户对某些设置或操作的访问或更改。可能会对一些敏感的设置进行限制,例如网络设置、安全设置等。也可能会对某些功能进行权限控制,只允许特定用户访问。

DashboardFragment是基础的仪表盘样式 UI Fragment,用于显示静态和动态的设置项列表。通常不会加入对设置的限制,而是专注于提供用户友好的设置界面和功能。可能包含一些基本的设置项处理和展示逻辑,以便用户能够轻松地进行配置和操作。

MobileNetworkSettings 移动网络设置(界面逻辑)

packages/apps/Settings/src/com/android/settings/network/telephony/MobileNetworkSettings.java

界面布局在mobile_network_settings.xml

| API | Function |
| onPreferenceTreeClick | 定义子功能pref点击事件(override) |
| createPreferenceControllers | 创建子功能pref的控制器(override) |
| onAttach | 生命周期,use 各类 Controller(override) |

onSubscriptionDetailChanged 响应注册状态变化进行的操作
[MobileNetworkSettings API]
java 复制代码
package com.android.settings.network.telephony;

@SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC)
public class MobileNetworkSettings extends AbstractMobileNetworkSettings implements
        MobileNetworkRepository.MobileNetworkCallback {

    private static final String LOG_TAG = "NetworkSettings";


    //处理子菜单点击事件
    /**
     * Invoked on each preference click in this hierarchy, overrides
     * PreferenceActivity's implementation.  Used to make sure we track the
     * preference click events.
     */
    @Override
    public boolean onPreferenceTreeClick(Preference preference) {
        if (super.onPreferenceTreeClick(preference)) {
            return true;
        }
        final String key = preference.getKey();

        if (TextUtils.equals(key, BUTTON_CDMA_SYSTEM_SELECT_KEY)
                || TextUtils.equals(key, BUTTON_CDMA_SUBSCRIPTION_KEY)) {
            if (mTelephonyManager.getEmergencyCallbackMode()) {
                startActivityForResult(
                        new Intent(TelephonyManager.ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS, null),
                        REQUEST_CODE_EXIT_ECM);
                mClickedPrefKey = key;
            }
            return true;
        }
        //【客制化】可通过else if 定制其他keyt的点击行为

        return false;
    }

    //创建各子菜单/功能选项的Controller
    @Override
    protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
        if (!SubscriptionUtil.isSimHardwareVisible(context)) {
            finish();
            return Arrays.asList();
        }
        if (getArguments() == null) {
            Intent intent = getIntent();
            if (intent != null) {
                mSubId = intent.getIntExtra(Settings.EXTRA_SUB_ID,
                        MobileNetworkUtils.getSearchableSubscriptionId(context));
                Log.d(LOG_TAG, "display subId from intent: " + mSubId);
            } else {
                Log.d(LOG_TAG, "intent is null, can not get subId " + mSubId + " from intent.");
            }
        } else {
            mSubId = getArguments().getInt(Settings.EXTRA_SUB_ID,
                    MobileNetworkUtils.getSearchableSubscriptionId(context));
            Log.d(LOG_TAG, "display subId from getArguments(): " + mSubId);
        }
        mMobileNetworkRepository = MobileNetworkRepository.getInstance(context);
        mExecutor.execute(() -> {
            mSubscriptionInfoEntity = mMobileNetworkRepository.getSubInfoById(
                    String.valueOf(mSubId));
            mMobileNetworkInfoEntity =
                    mMobileNetworkRepository.queryMobileNetworkInfoBySubId(
                            String.valueOf(mSubId));
        });

        return Arrays.asList(
                new DataUsageSummaryPreferenceController(getActivity(), getSettingsLifecycle(),
                        this, mSubId),
                new RoamingPreferenceController(context, KEY_ROAMING_PREF, getSettingsLifecycle(),
                        this, mSubId),
                new CallsDefaultSubscriptionController(context, KEY_CALLS_PREF,
                        getSettingsLifecycle(), this),
                new SmsDefaultSubscriptionController(context, KEY_SMS_PREF, getSettingsLifecycle(),
                        this),
                new MobileDataPreferenceController(context, KEY_MOBILE_DATA_PREF,
                        getSettingsLifecycle(), this, mSubId),
                new ConvertToEsimPreferenceController(context, KEY_CONVERT_TO_ESIM_PREF,
                        getSettingsLifecycle(), this, mSubId));
    }


    @Override
    public void onAttach(Context context) {
        super.onAttach(context);

        if (mSubId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
            Log.d(LOG_TAG, "Invalid subId, get the default subscription to show.");
            SubscriptionInfo info = SubscriptionUtil.getSubscriptionOrDefault(context, mSubId);
            if (info == null) {
                Log.d(LOG_TAG, "Invalid subId request " + mSubId);
                return;
            }
            mSubId = info.getSubscriptionId();
            Log.d(LOG_TAG, "Show NetworkSettings fragment for subId" + mSubId);
        }

        Intent intent = getIntent();
        if (intent != null) {
            int updateSubscriptionIndex = intent.getIntExtra(Settings.EXTRA_SUB_ID,
                    SubscriptionManager.INVALID_SUBSCRIPTION_ID);

            if (updateSubscriptionIndex != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
                int oldSubId = mSubId;
                mSubId = updateSubscriptionIndex;
                // If the subscription has changed or the new intent does not contain the opt in
                // action,
                // remove the old discovery dialog. If the activity is being recreated, we will see
                // onCreate -> onNewIntent, so the dialog will first be recreated for the old
                // subscription
                // and then removed.
                if (updateSubscriptionIndex != oldSubId
                        || !MobileNetworkActivity.doesIntentContainOptInAction(intent)) {
                    removeContactDiscoveryDialog(oldSubId);
                }

                // evaluate showing the new discovery dialog if this intent contains an action to
                // show the
                // opt-in.
                if (MobileNetworkActivity.doesIntentContainOptInAction(intent)) {
                    showContactDiscoveryDialog();
                }
            }

        }

        final DataUsageSummaryPreferenceController dataUsageSummaryPreferenceController =
                use(DataUsageSummaryPreferenceController.class);
        if (dataUsageSummaryPreferenceController != null) {
            dataUsageSummaryPreferenceController.init(mSubId);
        }
        use(MobileNetworkSwitchController.class).init(mSubId);
        use(CarrierSettingsVersionPreferenceController.class).init(mSubId);
        use(BillingCyclePreferenceController.class).init(mSubId);
        use(MmsMessagePreferenceController.class).init(mSubId);
        use(AutoDataSwitchPreferenceController.class).init(mSubId);
        use(DisabledSubscriptionController.class).init(mSubId);
        use(DeleteSimProfilePreferenceController.class).init(mSubId, this,
                REQUEST_CODE_DELETE_SUBSCRIPTION);
        use(DisableSimFooterPreferenceController.class).init(mSubId);
        use(NrDisabledInDsdsFooterPreferenceController.class).init(mSubId);

        final MobileDataPreferenceController mobileDataPreferenceController =
                use(MobileDataPreferenceController.class);
        if (mobileDataPreferenceController != null) {
            mobileDataPreferenceController.init(getFragmentManager(), mSubId,
                    mSubscriptionInfoEntity, mMobileNetworkInfoEntity);
            mobileDataPreferenceController.setWifiPickerTrackerHelper(
                    new WifiPickerTrackerHelper(getSettingsLifecycle(), context,
                            null /* WifiPickerTrackerCallback */));
        }

        final RoamingPreferenceController roamingPreferenceController =
                use(RoamingPreferenceController.class);
        if (roamingPreferenceController != null) {
            roamingPreferenceController.init(getFragmentManager(), mSubId,
                    mMobileNetworkInfoEntity);
        }
        use(ApnPreferenceController.class).init(mSubId);
        use(CarrierPreferenceController.class).init(mSubId);
        use(DataUsagePreferenceController.class).init(mSubId);
        use(PreferredNetworkModePreferenceController.class).init(mSubId);
        use(EnabledNetworkModePreferenceController.class).init(mSubId);
        use(DataServiceSetupPreferenceController.class).init(mSubId);
        use(Enable2gPreferenceController.class).init(mSubId);
        use(CarrierWifiTogglePreferenceController.class).init(getLifecycle(), mSubId);

        final WifiCallingPreferenceController wifiCallingPreferenceController =
                use(WifiCallingPreferenceController.class).init(mSubId);

        final OpenNetworkSelectPagePreferenceController openNetworkSelectPagePreferenceController =
                use(OpenNetworkSelectPagePreferenceController.class).init(mSubId);
        final AutoSelectPreferenceController autoSelectPreferenceController =
                use(AutoSelectPreferenceController.class)
                        .init(getLifecycle(), mSubId)
                        .addListener(openNetworkSelectPagePreferenceController);
        use(NetworkPreferenceCategoryController.class).init(mSubId)
                .setChildren(Arrays.asList(autoSelectPreferenceController));
        mCdmaSystemSelectPreferenceController = use(CdmaSystemSelectPreferenceController.class);
        mCdmaSystemSelectPreferenceController.init(getPreferenceManager(), mSubId);
        mCdmaSubscriptionPreferenceController = use(CdmaSubscriptionPreferenceController.class);
        mCdmaSubscriptionPreferenceController.init(getPreferenceManager(), mSubId);

        final VideoCallingPreferenceController videoCallingPreferenceController =
                use(VideoCallingPreferenceController.class).init(mSubId);
        use(CallingPreferenceCategoryController.class).setChildren(
                Arrays.asList(wifiCallingPreferenceController, videoCallingPreferenceController));
        use(Enhanced4gLtePreferenceController.class).init(mSubId)
                .addListener(videoCallingPreferenceController);
        use(Enhanced4gCallingPreferenceController.class).init(mSubId)
                .addListener(videoCallingPreferenceController);
        use(Enhanced4gAdvancedCallingPreferenceController.class).init(mSubId)
                .addListener(videoCallingPreferenceController);
        use(ContactDiscoveryPreferenceController.class).init(getParentFragmentManager(), mSubId);
        use(NrAdvancedCallingPreferenceController.class).init(mSubId);
        use(TransferEsimPreferenceController.class).init(mSubId, mSubscriptionInfoEntity);
        final ConvertToEsimPreferenceController convertToEsimPreferenceController =
                use(ConvertToEsimPreferenceController.class);
        if (convertToEsimPreferenceController != null) {
            convertToEsimPreferenceController.init(mSubId, mSubscriptionInfoEntity);
        }

        List<AbstractSubscriptionPreferenceController> subscriptionPreferenceControllers =
                useAll(AbstractSubscriptionPreferenceController.class);
        for (AbstractSubscriptionPreferenceController controller :
                subscriptionPreferenceControllers) {
            controller.init(mSubId);
        }
    }

    private void onSubscriptionDetailChanged() {
        if (mSubscriptionInfoEntity != null) {
            /**
             * Update the title when SIM stats got changed
             */
            final Consumer<Activity> renameTitle = activity -> {
                if (activity != null && !activity.isFinishing()) {
                    if (activity instanceof SettingsActivity) {
                        ((SettingsActivity) activity).setTitle(mSubscriptionInfoEntity.uniqueName);
                    }
                }
            };

            ThreadUtils.postOnMainThread(() -> {
                renameTitle.accept(getActivity());
                redrawPreferenceControllers();
            });
        }
    }

    //界面布局在mobile_network_settings.xml
    @Override
    protected int getPreferenceScreenResId() {
        return R.xml.mobile_network_settings;
    }

}

mobile_network_settings 布局

packages/apps/Settings/res/xml/mobile_network_settings.xml

  • MobileNetworkSwitchController SIM卡移动网络总开关逻辑
  • use_sim_switch 界面的资源key
  • mobile_network_use_sim_on 开关名称title

以下仅展示部分功能选项/菜单:

XML 复制代码
<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.widget.SettingsMainSwitchPreference
        android:key="use_sim_switch"
        android:title="@string/mobile_network_use_sim_on"
        settings:controller="com.android.settings.network.telephony.MobileNetworkSwitchController"/>

    <PreferenceCategory
        android:key="enabled_state_container"
        android:title="@string/summary_placeholder"
        settings:controller="com.android.settings.network.telephony.DisabledSubscriptionController"
        android:layout="@layout/preference_category_no_label">

        <!--智能数据切换开关-->
        <SwitchPreference
            android:key="auto_data_switch"
            android:title="@string/auto_data_switch_title"
            android:summary="@string/auto_data_switch_summary"
            settings:controller="com.android.settings.network.telephony.AutoDataSwitchPreferenceController"/>
        <!--数据漫游开关-->
        <com.android.settingslib.RestrictedSwitchPreference
            android:key="button_roaming_key"
            android:title="@string/roaming"
            android:persistent="false"
            android:summaryOn="@string/roaming_enable"
            android:summaryOff="@string/roaming_disable"
            settings:userRestriction="no_data_roaming"
            settings:controller="com.android.settings.network.telephony.RoamingPreferenceController"/>

        <!--数据流量菜单入口-->
        <Preference
            android:key="data_usage_summary"
            android:title="@string/mobile_data_usage_title"
            settings:controller="com.android.settings.network.telephony.DataUsagePreferenceController"/>

        <!--网络模式列表选择-->
        <ListPreference
            android:key="enabled_networks_key"
            android:title="@string/preferred_network_mode_title"
            android:summary="@string/preferred_network_mode_summary"
            android:entries="@array/enabled_networks_choices"
            android:entryValues="@array/enabled_networks_values"
            android:dialogTitle="@string/preferred_network_mode_dialogtitle"
            settings:controller="com.android.settings.network.telephony.EnabledNetworkModePreferenceController"/>

        <!--Network目录-->
        <PreferenceCategory
            android:key="network_operators_category_key"
            android:title="@string/network_operator_category"
            settings:controller="com.android.settings.network.telephony.NetworkPreferenceCategoryController">
            <!--自动选网开关-->
            <SwitchPreference
                android:key="auto_select_key"
                android:title="@string/select_automatically"
                settings:controller="com.android.settings.network.telephony.gsm.AutoSelectPreferenceController"/>
            <!--如果上述自动选网关闭,那么此手动选网菜单,可跳转到网络列表页-->
            <Preference
                android:key="choose_network_key"
                android:title="@string/choose_network_title"
                settings:controller="com.android.settings.network.telephony.gsm.OpenNetworkSelectPagePreferenceController"/>
        </PreferenceCategory>

        <!--APN设置入口-->
        <!--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:allowDividerAbove="true"
            settings:keywords="@string/keywords_access_point_names"
            settings:controller="com.android.settings.network.telephony.ApnPreferenceController"/>

    </PreferenceCategory>

</PreferenceScreen>
相关推荐
拭心2 小时前
Google 提供的 Android 端上大模型组件:MediaPipe LLM 介绍
android
带电的小王4 小时前
WhisperKit: Android 端测试 Whisper -- Android手机(Qualcomm GPU)部署音频大模型
android·智能手机·whisper·qualcomm
梦想平凡4 小时前
PHP 微信棋牌开发全解析:高级教程
android·数据库·oracle
元争栈道5 小时前
webview和H5来实现的android短视频(短剧)音视频播放依赖控件
android·音视频
阿甘知识库6 小时前
宝塔面板跨服务器数据同步教程:双机备份零停机
android·运维·服务器·备份·同步·宝塔面板·建站
元争栈道6 小时前
webview+H5来实现的android短视频(短剧)音视频播放依赖控件资源
android·音视频
MuYe6 小时前
Android Hook - 动态加载so库
android
居居飒7 小时前
Android学习(四)-Kotlin编程语言-for循环
android·学习·kotlin
Henry_He10 小时前
桌面列表小部件不能点击的问题分析
android
工程师老罗10 小时前
Android笔试面试题AI答之Android基础(1)
android