| <html devsite> |
| <head> |
| <title>Information Architecture</title> |
| <meta name="project_path" value="/_project.yaml" /> |
| <meta name="book_path" value="/_book.yaml" /> |
| </head> |
| <body> |
| {% include "_versions.html" %} |
| |
| <!-- |
| Copyright 2017 The Android Open Source Project |
| |
| Licensed under the Apache License, Version 2.0 (the "License"); |
| you may not use this file except in compliance with the License. |
| You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| --> |
| |
| <p> |
| Android 8.0 introduced a new information architecture for the Settings app to |
| simplify the way settings are organized and make it easier for users to |
| quickly find settings to customize their Android devices. |
| |
| Android {{ androidPVersionNumber }} introduced some improvements to provide more |
| Settings functionality and easier implementation. |
| </p> |
| |
| <h2 id="examples-and-source">Examples and source</h2> |
| |
| <p> |
| Most pages in Settings are currently implemented using the new framework. A good |
| example is DisplaySettings: |
| <code>packages/apps/Settings/src/com/android/settings/DisplaySettings.java</code> |
| </p> |
| |
| <p> |
| Files paths for important components are listed below: |
| </p> |
| |
| <ul> |
| <li><strong>CategoryKey</strong>: |
| <code>packages/SettingsLib/src/com/android/settingslib/drawer/CategoryKey.java</code></li> |
| <li><strong>DashboardFragmentRegistry</strong>: |
| <code>packages/apps/Settings/src/com/android/settings/dashboard/DashboardFragmentRegistry.java</code></li> |
| <li><strong>DashboardFragment</strong>: |
| <code>packages/apps/Settings/src/com/android/settings/dashboard/DashboardFragment.java</code></li> |
| <li><strong>AbstractPreferenceController</strong>: |
| <code>frameworks/base/packages/SettingsLib/src/com/android/settingslib/core/AbstractPreferenceController.java |
| </code></li> |
| <li><strong>BasePreferenceController</strong> (introduced in Android {{ androidPVersionNumber }}): |
| <code>packages/apps/Settings/src/com/android/settings/core/BasePreferenceController.java</code></li> |
| </ul> |
| |
| <h2 id="implementation">Implementation</h2> |
| |
| <p> |
| 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 |
| <code>SettingsPreferencePage</code>) to a new page (implemented using |
| <code>DashboardFragment</code>) can be complicated. The preference from the |
| legacy page is likely not implemented with a <code>PreferenceController</code>. |
| </p> |
| |
| <p> |
| So when moving a preferences from a legacy page to a new page, you need to create a |
| <code>PreferenceController</code> and move the code into the controller before |
| instantiating it in the new <code>DashboardFragment</code>. The APIs that |
| <code>PreferenceController</code> requires are described in their name and |
| documented in Javadoc. |
| </p> |
| |
| <p> |
| It is highly recommended to add a unit test for each <code>PreferenceController</code>. |
| If the change is submitted to AOSP, then a unit test is required. |
| To get more information about how to write Robolectric based tests, see the |
| readme file <code>packages/apps/Settings/tests/robotests/README.md</code>. |
| </p> |
| |
| <h3 id="plugin">Plugin-style information architecture</h3> |
| |
| <p> |
| Each settings item is implemented as a Preference. A Preference can easily be |
| moved from one page to another. |
| </p> |
| |
| <p> |
| To make it easier for multiple settings to be moved around, Android 8.0 introduced |
| 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. |
| </p> |
| |
| <h3 id="dashboard-fragment">DashboardFragment</h3> |
| |
| <p> |
| <code>DashboardFragment</code> is the host of plugin-style preference controllers. |
| The fragment inherits from <code>PreferenceFragment</code> and has hooks to |
| inflate and update both static preference lists and dynamic preference lists. |
| </p> |
| |
| <h3 id="static-preferences">Static preferences</h3> |
| |
| <p> |
| A static preference list is defined in XML using the <code><Preference></code> tag. A |
| <code>DashboardFragment</code> implementation uses the |
| <code>getPreferenceScreenResId()</code> method to define which XML file contains |
| the static list of preferences to display. |
| </p> |
| |
| <h3 id="dynamic-preferences">Dynamic preferences</h3> |
| |
| <p> |
| 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 <code>AndroidManifest</code> (discussed below) and loaded |
| through a <code>FeatureProvider</code> (defined as <code> |
| DashboardFeatureProvider</code>). |
| </p> |
| |
| <p> |
| 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: |
| </p> |
| |
| <ul> |
| <li>The setting is not directly implemented in the Settings app (such as |
| injecting a setting implemented by OEM/Carrier apps).</li> |
| <li>The setting should appear on the Settings homepage.</li> |
| <li>You already have an Activity for the setting and do not want to implement the |
| extra static config.</li> |
| </ul> |
| |
| <p> |
| To configure an Activity as a dynamic setting, do the following: |
| </p> |
| |
| <ul> |
| <li>Mark the activity as a dynamic setting by adding an intent-filter to the |
| activity.</li> |
| <li>Tell the Settings app which category it belongs to. The category is a constant, |
| defined in <code>CategoryKey</code>.</li> |
| <li>Optional: Add summary text when the setting is displayed.</li> |
| </ul> |
| |
| <p> |
| Here is an example taken from Settings app for <code>DisplaySettings</code>. |
| </p> |
| |
| <pre |
| class="prettyprint"> |
| <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> |
| </pre> |
| |
| <p> |
| At render time, the fragment will ask for a list of Preferences from both static |
| XML and dynamic settings defined in <code>AndroidManifest</code>. Whether the |
| <code>PreferenceController</code>s are defined in Java code or in XML, |
| <code>DashboardFragment</code> manages the handling logic of each setting |
| through <code>PreferenceController</code> (discussed below). Then they are |
| displayed in the UI as a mixed list. |
| </p> |
| |
| <h3 id="preference-controller">PreferenceController</h3> |
| |
| <p>There are differences between implementing <code>PreferenceController</code> |
| in Android {{ androidPVersionNumber }} and Android 8.x, as described in this |
| section.</p> |
| |
| <h4>PreferenceController in Android {{ androidPVersionNumber }} release</h4> |
| |
| <p>A <code>PreferenceController</code> contains all logic to interact with the |
| preference, including displaying, updating, search indexing, etc.</p> |
| |
| <p>The interface of <code>PreferenceController</code> is defined as |
| <code>BasePreferenceController</code>. For example, see code in |
| <code>packages/apps/Settings/src/com/android/settings/core/ |
| BasePreferenceController.java</code></p> |
| |
| <p>There are several subclasses of <code>BasePreferenceController</code>, each |
| mapping to a specific UI style that the Settings app supports by default. For |
| example, <code>TogglePreferenceController</code> has an API that directly maps |
| to how the user should interact with a toggle-based preference UI.</p> |
| |
| <p><code>BasePreferenceController</code> has APIs like |
| <code>getAvailabilityStatus()</code>, <code>displayPreference()</code>, |
| <code>handlePreferenceTreeClicked(),</code> etc. Detailed documentation for each |
| API is in the interface class.</p> |
| |
| <p>A restriction on implementing <code>BasePreferenceController</code> (and |
| its subclasses such as <code>TogglePreferenceController</code>) is that the |
| constructor signature must match either of the following:</p> |
| |
| <ul> |
| <li><code>public MyController(Context context, String key) {}</code></li> |
| <li><code>public MyController(Context context) {}</code></li> |
| </ul> |
| |
| <p>While installing a preference to the fragment, dashboard provides a method to |
| attach a <code>PreferenceController</code> before display time. At install time, |
| the controller is wired up to the fragment so all future relevant events are |
| sent to the controller.</p> |
| |
| <code>DashboardFragment</code> keeps a list of |
| <code>PreferenceController</code>s in the screen. At the fragment's |
| <code>onCreate()</code>, all controllers are invoked for the |
| <code>getAvailabilityStatus()</code> method, and if it returns true, |
| <code>displayPreference()</code> is invoked to process display logic. |
| <code>getAvailabilityStatus()</code> is also important to tell the Settings |
| framework which items are available during search.</p> |
| |
| <h4>PreferenceController in Android 8.x releases</h4> |
| |
| <p> |
| A <code>PreferenceController</code> contains all logic to interact with the |
| preference, including displaying, updating, search indexing. etc. |
| </p> |
| |
| <p> |
| Corresponding to the preference interactions, the interface of <code> |
| PreferenceController</code> has APIs <code>isAvailable()</code>, <code> |
| displayPreference()</code>, <code>handlePreferenceTreeClicked()</code> etc. |
| Detailed documentation on each API can be found in the interface class. |
| </p> |
| |
| <p> |
| While installing a preference to the fragment, dashboard provides a method to |
| attach a <code>PreferenceController</code> before display time. At install time, |
| the controller is wired up to the fragment so all future relevant events are |
| sent to the controller. |
| </p> |
| |
| <p> |
| <code>DashboardFragment</code> keeps a list of <code>PreferenceControllers |
| </code> in the screen. At the fragment's <code>onCreate()</code>, all |
| controllers are invoked for the <code>isAvailable()</code> method, and if it |
| returns true, <code>displayPreference()</code> is invoked to process display |
| logic. |
| </p> |
| |
| <h2 id="using-dashboardfragment">Using DashboardFragment</h2> |
| |
| <h3 id="moving-preference">Moving a preference from page A to B</h3> |
| |
| <p> |
| If the preference is statically listed in the original page's preference XML |
| file, follow the <strong>Static</strong> move procedure for your Android |
| release below. Otherwise, follow the <strong>Dynamic</strong> move procedure |
| for your Android release. |
| </p> |
| |
| <h4 id="static-move-p">Static move in Android {{ androidPVersionNumber }}</h4> |
| |
| <ol> |
| <li>Find the preference XML files for the original page and destination |
| page. You can find this information from the page's |
| <code>getPreferenceScreenResId()</code> method.</li> |
| <li>Remove the preference from the original page's XML.</li> |
| <li>Add the preference to the destination page's XML.</li> |
| <li>Remove the <code>PreferenceController</code> for this preference from the |
| original page's Java implementation. Usually it is in |
| <code>createPreferenceControllers()</code>. The controller might be declared in |
| XML directly. |
| <p><strong>Note</strong>: The preference might not have a |
| <code>PreferenceController</code>.</p></li> |
| <li>Instantiate the <code>PreferenceController</code> in the destination page's |
| <code>createPreferenceControllers()</code>. If the |
| <code>PreferenceController</code> is defined in XML in the old page, define it |
| in XML for the new page also.</li> |
| </ol> |
| |
| <h4 id="dynamic-move">Dynamic move in Android {{ androidPVersionNumber }}</h4> |
| |
| <ol> |
| <li>Find which category the original and destination page hosts. You can |
| find this information in <code>DashboardFragmentRegistry</code>.</li> |
| <li>Open the <code>AndroidManifest.xml</code> file that contains the setting you |
| need to move and find the Activity entry representing this setting.</li> |
| <li>Set the activity's metadata value for |
| <code>com.android.settings.category</code> to the new page's category key.</li> |
| </ol> |
| |
| <h4 id="static-move-8">Static move in Android 8.x releases</h4> |
| |
| <ol> |
| <li>Find the preference XML files for the original page and destination page.</li> |
| You can find this information from the page's <code>getPreferenceScreenResId() |
| </code> method.</li> |
| <li>Remove the preference in the original page's XML.</li> |
| <li>Add the preference to destination page's XML.</li> |
| <li>Remove the <code>PreferenceController</code> for this preference in the |
| original page's Java implementation. Usually it's in |
| <code>getPreferenceControllers()</code>.</li> |
| <p><strong>Note</strong>: It is possible the preference does not have a |
| <code>PreferenceController</code>.</p></li> |
| <li>Instantiate the <code>PreferenceController</code> in the destination page's |
| <code>getPreferenceControllers()</code>.</li> |
| </ol> |
| |
| <h4 id="dynamic-move">Dynamic move in Android 8.x releases</h4> |
| |
| <ol> |
| <li>Find which category the original and destination page hosts. You can find |
| this information in <code>DashboardFragmentRegistry</code>.</li> |
| <li>Open the <code>AndroidManifest.xml</code> file that contains the setting you |
| need to move and find the Activity entry representing this setting.</li> |
| <li>Change the activity's metadata value for <code>com.android.settings.category</code>, |
| set the value point to the new page's category key.</li> |
| </ol> |
| |
| <h3 id="creating-a-new-preference">Creating a new preference in a page</h3> |
| |
| <p> |
| If the preference is statically listed in the original page's preference XML |
| file, follow the <strong>static</strong> procedure below. Otherwise follow the |
| <strong>dynamic</strong> procedure. |
| </p> |
| |
| <h4 id="static-create">Creating a static preference</h4> |
| |
| <ol> |
| <li>Find the preference XML files for the page. You can find this information |
| from the page's getPreferenceScreenResId() method.</li> |
| <li>Add a new Preference item in the XML. Make sure it has a unique <code>android:key</code>.</li> |
| <li> |
| Define a <code>PreferenceController</code> for this preference in the page's |
| <code>getPreferenceControllers()</code> method. |
| <ul> |
| <li>In Android 8.x and optionally in Android {{ androidPVersionNumber }}, |
| instantiate a <code>PreferenceController</code> for this preference in the |
| page’s <code>createPreferenceControllers()</code> method. |
| |
| <p>If this preference already existed in other places, it’s possible there is |
| already a <code>PreferenceController</code> for it. You can reuse the |
| <code>PreferenceController</code> without building a new one.</p> |
| </li> |
| <li> |
| Starting in Android {{ androidPVersionNumber }}, you can choose to declare the |
| <code>PreferenceController</code> in XML next to the preference. For example: |
| <pre class="prettyprint"> |
| <Preference |
| android:key="reset_dashboard" |
| android:title="@string/reset_dashboard_title" |
| <b>settings:controller="com.android.settings.system.ResetPreferenceController"/></b> |
| </pre> |
| </li> |
| </ul> |
| </li> |
| </ol> |
| |
| <h4 id="dynamic-create">Creating a dynamic preference</h4> |
| |
| <ol> |
| <li>Find which category the original and destination page hosts. You can find |
| this information in <code>DashboardFragmentRegistry</code>.</li> |
| <li>Create a new Activity in <code>AndroidManifest</code></li> |
| <li>Add necessary metadata to the new Activity to define the setting. Set the |
| metadata value for <code>com.android.settings.category</code> to the same value |
| defined in step 1.</li> |
| </ol> |
| |
| <h3 id="create-new-page">Create a new page</h3> |
| <ol> |
| <li>Create a new fragment, inheriting from <code>DashboardFragment</code>.</li> |
| <li>Define its category in <code>DashboardFragmentRegistry</code>. |
| <p class="note"><strong>Note:</strong> This step is optional. If you do not need |
| any dynamic preferences in this page, you don't need to provide a category key.</p></li> |
| <li>Follow the steps for adding the settings needed for this page. For more |
| information, see the <a href="#implementation">Implementation</a> section.</li> |
| </ol> |
| |
| <h2 id="validation">Validation</h2> |
| |
| <ul> |
| <li>Run the robolectric tests in Settings. All existing and new tests should |
| pass. |
| <li>Build and install Settings, then manually open the page being modified. |
| The page should update immediately.</li> |
| </ul> |
| </body> |
| </html> |