Updated Information Architecture

Android 8.0 introduces a new information architecture for the Settings app. The goal of the new information architecture is to simplify the way settings are organized and make it easier for users to quickly find the settings needed to customize their Android devices.

Examples and source

Most pages in Settings are currently implemented using the new framework. A good example is DisplaySettings: packages/apps/Settings/src/com/android/settings/DisplaySettings.java

Files paths for important components are listed below:

CategoryKey

packages/SettingsLib/src/com/android/settingslib/drawer/CategoryKey.java

DashboardFragmentRegistry

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

DashboardFragment

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

AbstractPreferenceController and PreferenceController

frameworks/base/packages/SettingsLib/src/com/android/settingslib/core/AbstractPreferenceController.java packages/apps/Settings/src/com/android/settings/core/PreferenceController.java

Implementation

Device manufacturers are encouraged to adapt the existing Settings information architecture and insert additional settings pages as needed to accommodate partner-specific features. Moving preferences from legacy page (implemented as SettingsPreferencePage) to a new page (implemented using DashboardFragment) can be complicated. The preference from the legacy page is likely not implemented with a PreferenceController.

So when moving it into a DashboardFragment, partners will need to create a PreferenceController and move the code into the controller before instantiating it in the new DashboardFragment. The refactor process is fairly straightforward since most of it is just moving existing code.

Once done with refactoring, OEMs should submit patch CLs with tests to have their changes merged upstream.

Plugin-style information architecture

Each settings item is implemented as a Preference. A Preference can easily be moved from one page to another.

To make it easier for multiple settings to be moved around, Android O introduces a plugin style host fragment that contains settings items. Settings items are modeled as plugin-style controllers. Hence, a settings page is constructed by a single host fragment and multiple setting controllers.

DashboardFragment

This is the host of plugin-style preference controllers. The fragment inherits from PreferenceFragment and has hooks to inflate and update both static preference lists and dynamic preference lists.

Static preferences

A static preference list is defined in XML using the tag. A DashboardFragment implementation uses the getPreferenceScreenResId() method to define which XML file contains the static list of preferences to display.

Dynamic preferences

A dynamic item represents a tile with intent, leading to an external or internal Activity. Usually, the intent leads to a different setting page. For example, the "Google" setting item in the Settings homepage is a dynamic item. Dynamic items are defined in AndroidManifest (discussed below) and loaded through a FeatureProvider (defined as DashboardFeatureProvider).

Note that dynamic settings are more heavyweight than statically configured settings, so normally developers should implement the setting as a static one. However the dynamic setting can be useful when any of the following is true:

  • The setting is not directly implemented in the Settings app (such as injecting a setting implemented by OEM/Carrier apps).
  • The setting should appear on the Settings homepage.
  • You already have an Activity for the setting and don't want to implement the extra static config.

To configure an Activity as a dynamic setting, you need to do a few things:

  • Mark the activity as a dynamic setting. This is done by simply adding an intent-filter to the activity.
  • Tell Settings app which category it belongs to. The category is a constant, defined in CategoryKey.
  • Optional: Add a summary text when the setting is displayed.

Here is an example taken from Settings app for DisplaySettings.

<activity android:name="Settings$DisplaySettingsActivity"
                   android:label="@string/display_settings"
                   android:icon="@drawable/ic_settings_display">
             <!-- Mark the activity as a dynamic setting -->
              <intent-filter>
                     <action android:name="com.android.settings.action.IA_SETTINGS" />
              </intent-filter>
             <!-- Tell Settings app which category it belongs to -->
              <meta-data android:name="com.android.settings.category"
                     android:value="com.android.settings.category.ia.homepage" />
             <!-- Add a summary text when the setting is displayed -->
              <meta-data android:name="com.android.settings.summary"
                     android:resource="@string/display_dashboard_summary"/>
             </activity>

At render time, the fragment will ask for a list of Preferences from both static XML and dynamic settings defined in AndroidManifest. Regardless in which source a setting is loaded, DashboardFragment manages the handling logic of each setting through PreferenceController (discussed below). Then they will be displayed onto the UI as a mixed list.

PreferenceController

A PreferenceController contains all logic to interact with the preference, including displaying/updating/search indexing and so on.

Correspondingly, the interface of PreferenceController has API of isAvailable(), displayPreference(), handlePreferenceTreeClicked() etc. Detailed documentation on each API can be found in the interface class.

While installing a preference to the fragment, dashboard provides a method to attach a PreferenceController before display time. At install time, the controller will be wired up to the fragment so all future relevant events are sent to the controller.

DashboardFragment will keep a list of PreferenceControllers in the screen. At fragment's onCreate(), all controllers will be invoked for the isAvailable() method, and if it returns true, displayPreference() will be invoked to process display logic.

Using DashboardFragment

Moving preference from page A to B

If the preference is statically listed in the original page's preference XML file, follow the static path below. Otherwise, follow the dynamic path.

Static

  1. Find the preference XML files for the original page and destination page.
  2. You can find this information from the page's getPreferenceScreenResId() method.
  3. Remove the preference in the original page's XML.
  4. Add the preference to destination page's XML.
  5. Remove the PreferenceController for this preference in the original page's Java implementation. Usually it's in getPreferenceControllers().
  6. Note: It is possible the preference does not have a PreferenceController.
  7. Instantiate the PreferenceController in the destination page's getPreferenceControllers().

Dynamic

  1. Find which category the original and destination page hosts. You can find this information in DashboardFragmentRegistry.
  2. Open the AndroidManifest.xml file that contains the setting you need to move and find the Activity entry representing this setting.
  3. Change the activity's metadata value for "com.android.settings.category", set the value point to the new page's category key.

Creating a new preference in a page

If the preference is statically listed in the original page's preference XML file, follow the static path below. Otherwise follow the dynamic path.

Static

  1. Find the preference XML files for the page. You can find this information from the page's getPreferenceScreenResId() method.
  2. Add a new Preference item in the XML. Make sure it has a unique android:key.
  3. Instantiate a PreferenceController for this preference in the page's getPreferenceControllers() method.
  4. If this preference already existed in other places, it's possible there is already a PreferenceController for it. You can reuse the PreferenceController without building a new one.

Dynamic

  1. Find which category the original and destination page hosts. You can find this information in DashboardFragmentRegistry.
  2. Create a new Activity in AndroidManifest, and add necessary metadata to define the setting. Make the metadata value for "com.android.settings.category" to be the same value defined in step 1.

Create a new page

  1. Create a new fragment, inheriting from DashboardFragment.
  2. Define its category in DashboardFragmentRegistry.

    Note: This step is optional. If you do not need any dynamic preferences in this page, you don't need to provide a category key.

  3. Follow the steps for adding the settings needed for this page.

Validation

  • Run the robolectric tests in Settings, all existing and new tests should pass.
  • Build and install Settings, manually open the page being modified; the page should update immediately.