This paper mainly discusses the simple implementation of system Settings in Android 11, mainly including the following points:

  • System Settings Home page
  • Other pages for system Settings
  • Data control

First, the system Settings home page

Android system set up the main interface is com. Android Settings. The Settings, but it is just an activity – alias, points to the homepage. SettingsHomepageActivity.

<activity-alias android:name="Settings"
        android:label="@string/settings_label_launcher"
        android:taskAffinity="com.android.settings.root"
        android:launchMode="singleTask"
        android:targetActivity=".homepage.SettingsHomepageActivity">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
    <meta-data android:name="android.app.shortcuts" android:resource="@xml/shortcuts"/>
</activity-alias>
Copy the code

Need to be aware of is the command (adb shell dumpsys “window | grep mCurrentFocus”) to see the top mobile phone activity, not print out the targetActivity, but this activity – alias.

. Homepage. Logic is not complicated in SettingsHomepageActivity directly loading the TopLevelSettings the fragments

showFragment(new TopLevelSettings(), R.id.main_content);
Copy the code

TopLevelSettings displays the list of Settings through AndroidX Preference. The contents of the list of Settings are obtained through static configuration + dynamic addition.

1. Static configuration

The so-called static configuration is through XML configuration. If you still don’t understand the Preference, to: www.jianshu.com/p/348eb0928… Just a little bit

TopLevelSettings inherits from the abstract DashboardFragment class, implements the abstract method getPreferenceScreenResId(), and returns the preference configuration file to complete static configuration.

@Override
protected int getPreferenceScreenResId() {
    return R.xml.top_level_settings;
}
Copy the code

Top_level_settings specifies the configuration items to be displayed on the page:

<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" xmlns:settings="http://schemas.android.com/apk/res-auto" android:key="top_level_settings"> <Preference android:key="top_level_network" android:title="@string/network_dashboard_title" android:summary="@string/summary_placeholder" android:icon="@drawable/ic_homepage_network" android:order="-120" android:fragment="com.android.settings.network.NetworkDashboardFragment" settings:controller="com.android.settings.network.TopLevelNetworkEntryPreferenceController"/> <Preference android:key="top_level_connected_devices" android:title="@string/connected_devices_dashboard_title" android:summary="@string/summary_placeholder" android:icon="@drawable/ic_homepage_connected_device" android:order="-110"  android:fragment="com.android.settings.connecteddevice.ConnectedDeviceDashboardFragment" settings:controller="com.android.settings.connecteddevice.TopLevelConnectedDevicesPreferenceController"/> ... </PreferenceScreen>Copy the code

Among them:

  • key: Indicates the primary key of the configuration item
  • title: Indicates the title of the configuration item
  • summary: The text under the summary heading
  • icon: front icon
  • order: used for sorting. The smaller the value, the higher the ranking
  • fragment: Click the interface to jump to
  • controller: A controller for the item that controls its presentation, availability, and click events
2. Dynamic add

Dynamically obtain the applications that match the action tags in the system using the packageManger command and dynamically add them to the list.

For example, configuration items such as network traffic monitoring, storage space management, and default applications are added dynamically.

Specific implementation can see the article: MTK Fast Bully is dynamically loaded

Two, system Settings other interfaces

In addition to system Settings. Homepage. SettingsHomepageActivity, other most of the Activity are defined in the Settings, and inherited from SettingsActivity, but which did not implement any logic. Therefore, the logic for these activities is implemented in SettingsActivity.

/** * Top-level Settings activity */ public class Settings extends SettingsActivity { /* * Settings subclasses for launching independently. */ public static class AssistGestureSettingsActivity extends SettingsActivity { /* empty */} public static class BluetoothSettingsActivity extends SettingsActivity { /* empty */ } public static class CreateShortcutActivity extends SettingsActivity { /* empty */ } public static class FaceSettingsActivity extends SettingsActivity { /* empty */ } public static class FingerprintSettingsActivity extends SettingsActivity { /* empty */ }... }Copy the code

These activities do not implement any logic, so how does it load into its proper layout?

In the onCreate() of the parent SettingsActivity class:

@Override protected void onCreate(Bundle savedState) { ... // Should happen before any call to getIntent() // First step getMetaData(); // Final Intent Intent = getIntent(); if (intent.hasExtra(EXTRA_UI_OPTIONS)) { getWindow().setUiOptions(intent.getIntExtra(EXTRA_UI_OPTIONS, 0)); } // Getting Intent properties can only be done after the super.onCreate(...) final String initialFragmentName = intent.getStringExtra(EXTRA_SHOW_FRAGMENT); . // launchSettingFragment(initialFragmentName, Intent); . }Copy the code

Follow the three steps above:

The first step

First, getMetaData() obtains the fragment configured in the manifest of the Activity and assigns it to the mFragmentClass

public static final String META_DATA_KEY_FRAGMENT_CLASS = "com.android.settings.FRAGMENT_CLASS"; private void getMetaData() { try { ActivityInfo ai = getPackageManager().getActivityInfo(getComponentName(), PackageManager.GET_META_DATA); if (ai == null || ai.metaData == null) return; mFragmentClass = ai.metaData.getString(META_DATA_KEY_FRAGMENT_CLASS); } catch (NameNotFoundException nnfe) { // No recovery Log.d(LOG_TAG, "Cannot get Metadata for: " + getComponentName().toString()); }}Copy the code

So how is it configured in the manifest? As follows:

<activity android:name=".Settings$WifiInfoActivity"> <intent-filter> <action android:name="android.intent.action.MAIN"/>  <category android:name="android.intent.category.DEVELOPMENT_PREFERENCE" /> <category android:name="android.intent.category.DEFAULT" /> </intent-filter> <meta-data android:name="com.android.settings.FRAGMENT_CLASS" android:value="com.android.settings.wifi.WifiInfo" /> </activity>Copy the code

Therefore WifiInfoActivity the Acitivity corresponding fragment is: com. Android. Settings. Wifi. WifiInfo

The second step

Construct an intent with the EXTRA_SHOW_FRAGMENT through getIntent()

public Intent getIntent() { Intent superIntent = super.getIntent(); String startingFragment = getStartingFragmentClass(superIntent); // This is called from super.onCreate, isMultiPane() is not yet reliable // Do not use onIsHidingHeaders either, which relies itself on this method if (startingFragment ! = null) { Intent modIntent = new Intent(superIntent); modIntent.putExtra(EXTRA_SHOW_FRAGMENT, startingFragment); Bundle args = superIntent.getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS); if (args ! = null) { args = new Bundle(args); } else { args = new Bundle(); } args.putParcelable("intent", superIntent); modIntent.putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, args); return modIntent; } return superIntent; } /** * Checks if the component name in the intent is different from the Settings class and * returns the class name to Load as a fragment. */ private String getStartingFragmentClass(Intent Intent) {// Return if mFragmentClass exists (mFragmentClass ! = null) return mFragmentClass; String intentClass = intent.getComponent().getClassName(); if (intentClass.equals(getClass().getName())) return null; if ("com.android.settings.RunningServices".equals(intentClass) || "com.android.settings.applications.StorageUse".equals(intentClass)) { // Old names of manage apps. intentClass = ManageApplications.class.getName(); } return intentClass; }Copy the code

This includes the case where mFragmentClass is empty, but forget it for now.

The third step

Launch the Fragment with the launchSettingFragment(). The initialFragmentName parameter is the EXTRA_SHOW_FRAGMENT parameter included in the second Intent. If the mFragmentClass is not empty, it is passed the mFragmentClass

void launchSettingFragment(String initialFragmentName, Intent intent) {
    if (initialFragmentName != null) {
        setTitleFromIntent(intent);

        Bundle initialArguments = intent.getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS);
        switchToFragment(initialFragmentName, initialArguments, true,
                mInitialTitleResId, mInitialTitle);
    } else {
        // Show search icon as up affordance if we are displaying the main Dashboard
        mInitialTitleResId = R.string.dashboard_title;
        switchToFragment(TopLevelSettings.class.getName(), null /* args */, false,
                mInitialTitleResId, mInitialTitle);
    }
}
Copy the code

Add the fragment to the activity in switchToFragment().

/** * Switch to a specific Fragment with taking care of validation, Title and BackStack */ private Fragment switchToFragment(String fragmentName, Bundle args, boolean validate, int titleResId, CharSequence title) { Log.d(LOG_TAG, "Switching to fragment " + fragmentName); if (validate && ! isValidFragment(fragmentName)) { throw new IllegalArgumentException("Invalid fragment for this activity: " + fragmentName); Fragment f = utils.gettargetFragment (this, fragmentName, args); FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); transaction.replace(R.id.main_content, f); if (titleResId > 0) { transaction.setBreadCrumbTitle(titleResId); } else if (title ! = null) { transaction.setBreadCrumbTitle(title); } / / to commit the transaction transaction.com mitAllowingStateLoss (); getSupportFragmentManager().executePendingTransactions(); Log.d(LOG_TAG, "Executed frag manager pendingTransactions"); return f; }Copy the code

Third, data control

On the home page – Battery Settings can display real-time power. So how is it implemented?

First look at how it is configured:

<Preference
    android:key="top_level_battery"
    android:title="@string/power_usage_summary_title"
    android:summary="@string/summary_placeholder"
    android:icon="@drawable/ic_homepage_battery"
    android:fragment="com.android.settings.fuelgauge.PowerUsageSummary"
    android:order="-90"
    settings:controller="com.android.settings.fuelgauge.TopLevelBatteryPreferenceController"/>
Copy the code

TopLevelBatteryPreferenceController controller was configured in the configuration items, it inherits from AbstractPreferenceController, this abstract class is used for unified management of all menu items on (for example to show or hide, listening to the click event, etc.).

TopLevelBatteryPreferenceController code is as follows:

public class TopLevelBatteryPreferenceController extends BasePreferenceController implements LifecycleObserver, OnStart, OnStop {/ / power change radio private final BatteryBroadcastReceiver mBatteryBroadcastReceiver; // The current configuration item private Preference mPreference; // Private BatteryInfo mBatteryInfo; public TopLevelBatteryPreferenceController(Context context, String preferenceKey) { super(context, preferenceKey); mBatteryBroadcastReceiver = new BatteryBroadcastReceiver(mContext); mBatteryBroadcastReceiver.setBatteryChangedListener(type -> { BatteryInfo.getBatteryInfo(mContext, info -> { mBatteryInfo = info; updateState(mPreference); }, true /* shortString */); }); } @override public int getAvailabilityStatus() {return mContext.getResources().getBoolean(R.bool.config_show_top_level_battery) ? AVAILABLE : UNSUPPORTED_ON_DEVICE; } @Override public void displayPreference(PreferenceScreen screen) { super.displayPreference(screen); MPreference = screen.findPreference(getPreferenceKey()); } @ Override public void onStart () {/ / registered radio mBatteryBroadcastReceiver. Register (); } @ Override public void onStop () {/ / cancellation registration radio mBatteryBroadcastReceiver unRegister (); } @override public CharSequence getSummary() {return getDashboardLabel(mContext, mBatteryInfo); } static CharSequence getDashboardLabel(Context Context, BatteryInfo info) { if (info == null || context == null) { return null; } CharSequence label; if (! info.discharging && info.chargeLabel ! = null) { label = info.chargeLabel; } else if (info.remainingLabel == null) { label = info.batteryPercentString; } else { label = context.getString(R.string.power_remaining_settings_home_page, info.batteryPercentString, info.remainingLabel); } return label; }}Copy the code

The code is simple:

  1. Initialize the power change broadcast in the construction method
  2. inonStart()andonStop()To register and unregister a broadcast
  3. Once the power change broadcast is received, the power information is saved inmBatteryInfoIn the
  4. Then performupdateState(), the method is calledgetSummary()Sets the information to the current configuration item
  5. getSummary()Lt.mBatteryInfoThe stored electricity information is resolved
Summary:

Menu items such as display, hide, listening to the click event is done through inheritance from AbstractPreferenceController controller, the controller is configured in the XML, sometimes sometimes is dynamically add in fragments.

Disclaimer: Recently made a period of time Android 11 system setup application related development, will be related to the code of a simple combing summary, explain the improper place also please big guy pointed out

Reference: blog.csdn.net/qq_34149526…