【笔记】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>
相关推荐
千里马学框架4 天前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台4 天前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone4 天前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc4 天前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo4 天前
Kotlin 协程中的 Job 结构化并发与取消
android
sun0077004 天前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
其实防守也摸鱼4 天前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
AFinalStone5 天前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui
JMchen5 天前
属性动画原理与高级动画实现
android·kotlin·canvas
AFinalStone5 天前
Android7 SystemUI 源码解析(二)启动流程深度解析
android·systemui