/packages/apps/Settings/AndroidManifest.xml
.................................................................................
activity-alias是Android里为了重复使用Activity而设计的。对于activity-alias标签,它有一个属性叫android:targentActivity,这个属性就是用来为该标签设置目标Activity的,android:targetActivity 该属性指定了目标Activity,即通过activity-alias调起的Activity是哪个。
通过目标activity我们进入SettingsHomepageActivity
/packages/apps/Settings/src/com/android/settings/homepage/SettingsHomepageActivity.java
package com.android.settings.homepage;
import android.animation.LayoutTransition;
import android.app.ActivityManager;
import android.app.settings.SettingsEnums;
import android.os.Bundle;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.Toolbar;
import androidx.annotation.VisibleForTesting;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import com.android.settings.R;
import com.android.settings.accounts.AvatarViewMixin;
import com.android.settings.homepage.contextualcards.ContextualCardsFragment;
import com.android.settings.overlay.FeatureFactory;
public class SettingsHomepageActivity extends FragmentActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.settings_homepage_container);
final View root = findViewById(R.id.settings_homepage_container);
root.setSystemUiVisibility(
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
setHomepageContainerPaddingTop();
final Toolbar toolbar = findViewById(R.id.search_action_bar);
FeatureFactory.getFactory(this).getSearchFeatureProvider()
.initSearchToolbar(this /* activity */, toolbar, SettingsEnums.SETTINGS_HOMEPAGE);
final ImageView avatarView = findViewById(R.id.account_avatar);
final AvatarViewMixin avatarViewMixin = new AvatarViewMixin(this, avatarView);
getLifecycle().addObserver(avatarViewMixin);
if (!getSystemService(ActivityManager.class).isLowRamDevice()) {
// Only allow contextual feature on high ram devices.
showFragment(new ContextualCardsFragment(), R.id.contextual_cards_content);
}
showFragment(new TopLevelSettings(), R.id.main_content);
((FrameLayout) findViewById(R.id.main_content))
.getLayoutTransition().enableTransitionType(LayoutTransition.CHANGING);
}
private void showFragment(Fragment fragment, int id) {
final FragmentManager fragmentManager = getSupportFragmentManager();
final FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
final Fragment showFragment = fragmentManager.findFragmentById(id);
if (showFragment == null) {
fragmentTransaction.add(id, fragment);
} else {
fragmentTransaction.show(showFragment);
}
fragmentTransaction.commit();
}
@VisibleForTesting
void setHomepageContainerPaddingTop() {
final View view = this.findViewById(R.id.homepage_container);
final int searchBarHeight = getResources().getDimensionPixelSize(R.dimen.search_bar_height);
final int searchBarMargin = getResources().getDimensionPixelSize(R.dimen.search_bar_margin);
// The top padding is the height of action bar(48dp) + top/bottom margins(16dp)
final int paddingTop = searchBarHeight + searchBarMargin * 2;
view.setPadding(0 /* left */, paddingTop, 0 /* right */, 0 /* bottom */);
}
}
加载布局文件settings_homepage_container,调用showFragment方法显示TopLevelSettings。
package com.android.settings.homepage;
import static com.android.settings.search.actionbar.SearchMenuController.NEED_SEARCH_ICON_IN_ACTION_BAR;
import static com.android.settingslib.search.SearchIndexable.MOBILE;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.os.Bundle;
import android.provider.SearchIndexableResource;
import androidx.fragment.app.Fragment;
import androidx.preference.Preference;
import androidx.preference.PreferenceFragmentCompat;
import com.android.settings.R;
import com.android.settings.core.SubSettingLauncher;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settings.support.SupportPreferenceController;
import com.android.settingslib.core.instrumentation.Instrumentable;
import com.android.settingslib.search.SearchIndexable;
import java.util.Arrays;
import java.util.List;
@SearchIndexable(forTarget = MOBILE)
public class TopLevelSettings extends DashboardFragment implements
PreferenceFragmentCompat.OnPreferenceStartFragmentCallback {
private static final String TAG = "TopLevelSettings";
public TopLevelSettings() {
final Bundle args = new Bundle();
// Disable the search icon because this page uses a full search view in actionbar.
args.putBoolean(NEED_SEARCH_ICON_IN_ACTION_BAR, false);
setArguments(args);
}
@Override
protected int getPreferenceScreenResId() {
return R.xml.top_level_settings;
}
@Override
protected String getLogTag() {
return TAG;
}
@Override
public int getMetricsCategory() {
return SettingsEnums.DASHBOARD_SUMMARY;
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
use(SupportPreferenceController.class).setActivity(getActivity());
}
@Override
public int getHelpResource() {
// Disable the help icon because this page uses a full search view in actionbar.
return 0;
}
@Override
public Fragment getCallbackFragment() {
return this;
}
@Override
public boolean onPreferenceStartFragment(PreferenceFragmentCompat caller, Preference pref) {
new SubSettingLauncher(getActivity())
.setDestination(pref.getFragment())
.setArguments(pref.getExtras())
.setSourceMetricsCategory(caller instanceof Instrumentable
? ((Instrumentable) caller).getMetricsCategory()
: Instrumentable.METRICS_CATEGORY_UNKNOWN)
.setTitleRes(-1)
.launch();
return true;
}
@Override
protected boolean shouldForceRoundedIcon() {
return getContext().getResources()
.getBoolean(R.bool.config_force_rounded_icon_TopLevelSettings);
}
public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
new BaseSearchIndexProvider() {
@Override
public List getXmlResourcesToIndex(
Context context, boolean enabled) {
final SearchIndexableResource sir = new SearchIndexableResource(context);
sir.xmlResId = R.xml.top_level_settings;
return Arrays.asList(sir);
}
@Override
protected boolean isPageSearchEnabled(Context context) {
// Never searchable, all entries in this page are already indexed elsewhere.
return false;
}
};
}
加载布局文件top_level_settings。
/packages/apps/Settings/res/xml/top_level_settings.xml
top_level_settings主要配置一些Preference菜单项如网络、已连接的设备、通知、电池、显示、声音等等
attr | description |
android:key | 选项的名称,也是用来存储时唯一的key。 |
android:title | 选项的标题 |
android:summary | 摘要,配置的简要说明,显示在标题下面。 |
android:icon | 指定左侧的图标。 |
android:order | 偏好的顺序。如果不指定,默认的顺序将字母。 |
android:fragment | 指定fragment |
settings:controller | 控制管理类 |
TopLevelSettings继承自DashboardFragment.java,进入DashboardFragment.java看看。
package com.android.settings.dashboard;
import android.app.Activity;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
import androidx.annotation.CallSuper;
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
import androidx.preference.PreferenceGroup;
import androidx.preference.PreferenceManager;
import androidx.preference.PreferenceScreen;
import com.android.settings.R;
import com.android.settings.SettingsPreferenceFragment;
import com.android.settings.core.BasePreferenceController;
import com.android.settings.core.PreferenceControllerListHelper;
import com.android.settings.core.SettingsBaseActivity;
import com.android.settings.overlay.FeatureFactory;
import com.android.settings.search.Indexable;
import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.core.lifecycle.Lifecycle;
import com.android.settingslib.core.lifecycle.LifecycleObserver;
import com.android.settingslib.drawer.DashboardCategory;
import com.android.settingslib.drawer.Tile;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Base fragment for dashboard style UI containing a list of static and dynamic setting items.
*/
public abstract class DashboardFragment extends SettingsPreferenceFragment
implements SettingsBaseActivity.CategoryListener, Indexable,
SummaryLoader.SummaryConsumer, PreferenceGroup.OnExpandButtonClickListener,
BasePreferenceController.UiBlockListener {
private static final String TAG = "DashboardFragment";
private final Map> mPreferenceControllers =
new ArrayMap<>();
private final Set mDashboardTilePrefKeys = new ArraySet<>();
private DashboardFeatureProvider mDashboardFeatureProvider;
private DashboardTilePlaceholderPreferenceController mPlaceholderPreferenceController;
private boolean mListeningToCategoryChange;
private SummaryLoader mSummaryLoader;
private List mSuppressInjectedTileKeys;
@VisibleForTesting
UiBlockerController mBlockerController;
@Override
public void onAttach(Context context) {
super.onAttach(context);
mSuppressInjectedTileKeys = Arrays.asList(context.getResources().getStringArray(
R.array.config_suppress_injected_tile_keys));
mDashboardFeatureProvider = FeatureFactory.getFactory(context).
getDashboardFeatureProvider(context);
final List controllers = new ArrayList<>();
// Load preference controllers from code
final List controllersFromCode =
createPreferenceControllers(context);
// Load preference controllers from xml definition
final List controllersFromXml = PreferenceControllerListHelper
.getPreferenceControllersFromXml(context, getPreferenceScreenResId());
// Filter xml-based controllers in case a similar controller is created from code already.
final List uniqueControllerFromXml =
PreferenceControllerListHelper.filterControllers(
controllersFromXml, controllersFromCode);
// Add unique controllers to list.
if (controllersFromCode != null) {
controllers.addAll(controllersFromCode);
}
controllers.addAll(uniqueControllerFromXml);
// And wire up with lifecycle.
final Lifecycle lifecycle = getSettingsLifecycle();
uniqueControllerFromXml
.stream()
.filter(controller -> controller instanceof LifecycleObserver)
.forEach(
controller -> lifecycle.addObserver((LifecycleObserver) controller));
mPlaceholderPreferenceController =
new DashboardTilePlaceholderPreferenceController(context);
controllers.add(mPlaceholderPreferenceController);
for (AbstractPreferenceController controller : controllers) {
addPreferenceController(controller);
}
checkUiBlocker(controllers);
}
@VisibleForTesting
void checkUiBlocker(List controllers) {
final List keys = new ArrayList<>();
controllers
.stream()
.filter(controller -> controller instanceof BasePreferenceController.UiBlocker)
.forEach(controller -> {
((BasePreferenceController) controller).setUiBlockListener(this);
keys.add(controller.getPreferenceKey());
});
if (!keys.isEmpty()) {
mBlockerController = new UiBlockerController(keys);
mBlockerController.start(()->updatePreferenceVisibility(mPreferenceControllers));
}
}
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
// Set ComparisonCallback so we get better animation when list changes.
getPreferenceManager().setPreferenceComparisonCallback(
new PreferenceManager.SimplePreferenceComparisonCallback());
if (icicle != null) {
// Upon rotation configuration change we need to update preference states before any
// editing dialog is recreated (that would happen before onResume is called).
updatePreferenceStates();
}
}
@Override
public void onCategoriesChanged() {
final DashboardCategory category =
mDashboardFeatureProvider.getTilesForCategory(getCategoryKey());
if (category == null) {
return;
}
refreshDashboardTiles(getLogTag());
}
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
refreshAllPreferences(getLogTag());
}
@Override
public void onStart() {
super.onStart();
final DashboardCategory category =
mDashboardFeatureProvider.getTilesForCategory(getCategoryKey());
if (category == null) {
return;
}
if (mSummaryLoader != null) {
// SummaryLoader can be null when there is no dynamic tiles.
mSummaryLoader.setListening(true);
}
final Activity activity = getActivity();
if (activity instanceof SettingsBaseActivity) {
mListeningToCategoryChange = true;
((SettingsBaseActivity) activity).addCategoryListener(this);
}
}
@Override
public void notifySummaryChanged(Tile tile) {
final String key = mDashboardFeatureProvider.getDashboardKeyForTile(tile);
final Preference pref = getPreferenceScreen().findPreference(key);
if (pref == null) {
Log.d(getLogTag(), String.format(
"Can't find pref by key %s, skipping update summary %s",
key, tile.getDescription()));
return;
}
pref.setSummary(tile.getSummary(pref.getContext()));
}
@Override
public void onResume() {
super.onResume();
updatePreferenceStates();
}
@Override
public boolean onPreferenceTreeClick(Preference preference) {
Collection> controllers =
mPreferenceControllers.values();
// If preference contains intent, log it before handling.
mMetricsFeatureProvider.logDashboardStartIntent(
getContext(), preference.getIntent(), getMetricsCategory());
// Give all controllers a chance to handle click.
for (List controllerList : controllers) {
for (AbstractPreferenceController controller : controllerList) {
if (controller.handlePreferenceTreeClick(preference)) {
return true;
}
}
}
return super.onPreferenceTreeClick(preference);
}
@Override
public void onStop() {
super.onStop();
if (mSummaryLoader != null) {
// SummaryLoader can be null when there is no dynamic tiles.
mSummaryLoader.setListening(false);
}
if (mListeningToCategoryChange) {
final Activity activity = getActivity();
if (activity instanceof SettingsBaseActivity) {
((SettingsBaseActivity) activity).remCategoryListener(this);
}
mListeningToCategoryChange = false;
}
}
@Override
protected abstract int getPreferenceScreenResId();
@Override
public void onExpandButtonClick() {
mMetricsFeatureProvider.action(SettingsEnums.PAGE_UNKNOWN,
SettingsEnums.ACTION_SETTINGS_ADVANCED_BUTTON_EXPAND,
getMetricsCategory(), null, 0);
}
protected boolean shouldForceRoundedIcon() {
return false;
}
protected T use(Class clazz) {
List controllerList = mPreferenceControllers.get(clazz);
if (controllerList != null) {
if (controllerList.size() > 1) {
Log.w(TAG, "Multiple controllers of Class " + clazz.getSimpleName()
+ " found, returning first one.");
}
return (T) controllerList.get(0);
}
return null;
}
protected void addPreferenceController(AbstractPreferenceController controller) {
if (mPreferenceControllers.get(controller.getClass()) == null) {
mPreferenceControllers.put(controller.getClass(), new ArrayList<>());
}
mPreferenceControllers.get(controller.getClass()).add(controller);
}
/**
* Returns the CategoryKey for loading {@link DashboardCategory} for this fragment.
*/
@VisibleForTesting
public String getCategoryKey() {
return DashboardFragmentRegistry.PARENT_TO_CATEGORY_KEY_MAP.get(getClass().getName());
}
/**
* Get the tag string for logging.
*/
protected abstract String getLogTag();
/**
* Get a list of {@link AbstractPreferenceController} for this fragment.
*/
protected List createPreferenceControllers(Context context) {
return null;
}
/**
* Returns true if this tile should be displayed
*/
@CallSuper
protected boolean displayTile(Tile tile) {
if (mSuppressInjectedTileKeys != null && tile.hasKey()) {
// For suppressing injected tiles for OEMs.
return !mSuppressInjectedTileKeys.contains(tile.getKey(getContext()));
}
return true;
}
/**
* Displays resource based tiles.
*/
private void displayResourceTiles() {
final int resId = getPreferenceScreenResId();
if (resId <= 0) {
return;
}
addPreferencesFromResource(resId);
final PreferenceScreen screen = getPreferenceScreen();
screen.setOnExpandButtonClickListener(this);
mPreferenceControllers.values().stream().flatMap(Collection::stream).forEach(
controller -> controller.displayPreference(screen));
}
/**
* Update state of each preference managed by PreferenceController.
*/
protected void updatePreferenceStates() {
final PreferenceScreen screen = getPreferenceScreen();
Collection> controllerLists =
mPreferenceControllers.values();
for (List controllerList : controllerLists) {
for (AbstractPreferenceController controller : controllerList) {
if (!controller.isAvailable()) {
continue;
}
final String key = controller.getPreferenceKey();
if (TextUtils.isEmpty(key)) {
Log.d(TAG, String.format("Preference key is %s in Controller %s",
key, controller.getClass().getSimpleName()));
continue;
}
final Preference preference = screen.findPreference(key);
if (preference == null) {
Log.d(TAG, String.format("Cannot find preference with key %s in Controller %s",
key, controller.getClass().getSimpleName()));
continue;
}
controller.updateState(preference);
}
}
}
/**
* Refresh all preference items, including both static prefs from xml, and dynamic items from
* DashboardCategory.
*/
private void refreshAllPreferences(final String TAG) {
final PreferenceScreen screen = getPreferenceScreen();
// First remove old preferences.
if (screen != null) {
// Intentionally do not cache PreferenceScreen because it will be recreated later.
screen.removeAll();
}
// Add resource based tiles.
displayResourceTiles();
refreshDashboardTiles(TAG);
final Activity activity = getActivity();
if (activity != null) {
Log.d(TAG, "All preferences added, reporting fully drawn");
activity.reportFullyDrawn();
}
updatePreferenceVisibility(mPreferenceControllers);
}
@VisibleForTesting
void updatePreferenceVisibility(
Map> preferenceControllers) {
final PreferenceScreen screen = getPreferenceScreen();
if (screen == null || preferenceControllers == null || mBlockerController == null) {
return;
}
final boolean visible = mBlockerController.isBlockerFinished();
for (List controllerList :
preferenceControllers.values()) {
for (AbstractPreferenceController controller : controllerList) {
final String key = controller.getPreferenceKey();
final Preference preference = findPreference(key);
if (preference != null) {
preference.setVisible(visible && controller.isAvailable());
}
}
}
}
/**
* Refresh preference items backed by DashboardCategory.
*/
@VisibleForTesting
void refreshDashboardTiles(final String TAG) {
final PreferenceScreen screen = getPreferenceScreen();
final DashboardCategory category =
mDashboardFeatureProvider.getTilesForCategory(getCategoryKey());
if (category == null) {
Log.d(TAG, "NO dashboard tiles for " + TAG);
return;
}
final List tiles = category.getTiles();
if (tiles == null) {
Log.d(TAG, "tile list is empty, skipping category " + category.key);
return;
}
// Create a list to track which tiles are to be removed.
final List remove = new ArrayList<>(mDashboardTilePrefKeys);
// There are dashboard tiles, so we need to install SummaryLoader.
if (mSummaryLoader != null) {
mSummaryLoader.release();
}
final Context context = getContext();
mSummaryLoader = new SummaryLoader(getActivity(), getCategoryKey());
mSummaryLoader.setSummaryConsumer(this);
// Install dashboard tiles.
final boolean forceRoundedIcons = shouldForceRoundedIcon();
for (Tile tile : tiles) {
final String key = mDashboardFeatureProvider.getDashboardKeyForTile(tile);
if (TextUtils.isEmpty(key)) {
Log.d(TAG, "tile does not contain a key, skipping " + tile);
continue;
}
if (!displayTile(tile)) {
continue;
}
if (mDashboardTilePrefKeys.contains(key)) {
// Have the key already, will rebind.
final Preference preference = screen.findPreference(key);
mDashboardFeatureProvider.bindPreferenceToTile(getActivity(), forceRoundedIcons,
getMetricsCategory(), preference, tile, key,
mPlaceholderPreferenceController.getOrder());
} else {
// Don't have this key, add it.
final Preference pref = new Preference(getPrefContext());
mDashboardFeatureProvider.bindPreferenceToTile(getActivity(), forceRoundedIcons,
getMetricsCategory(), pref, tile, key,
mPlaceholderPreferenceController.getOrder());
screen.addPreference(pref);
mDashboardTilePrefKeys.add(key);
}
remove.remove(key);
}
// Finally remove tiles that are gone.
for (String key : remove) {
mDashboardTilePrefKeys.remove(key);
final Preference preference = screen.findPreference(key);
if (preference != null) {
screen.removePreference(preference);
}
}
mSummaryLoader.setListening(true);
}
@Override
public void onBlockerWorkFinished(BasePreferenceController controller) {
mBlockerController.countDown(controller.getPreferenceKey());
}
}
进入DashboardFragment.java后,在onAttach方法中完成preferenceControllers的加载。调用createPreferenceControllers方法,由源码可知此方法返回null,故只创建了一个空的controllersFromCode集合。调用getPreferenceControllersFromXml方法解析子类的资源文件(top_level_settings.xml),拿到xml中定义的controller,然后添加到controllers集合中,再返回controllers集合。通过PreferenceControllerListHelper.filterControllers过滤掉重复的controller。把过滤后的controller通过addPreferenceController添加到mPreferenceControllers
/packages/apps/Settings/src/com/android/settings/core/PreferenceControllerListHelper.java
package com.android.settings.core;
import static com.android.settings.core.PreferenceXmlParserUtils.METADATA_CONTROLLER;
import static com.android.settings.core.PreferenceXmlParserUtils.METADATA_KEY;
import android.annotation.NonNull;
import android.annotation.XmlRes;
import android.content.Context;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import com.android.settings.core.PreferenceXmlParserUtils.MetadataFlag;
import com.android.settingslib.core.AbstractPreferenceController;
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
/**
* Helper to load {@link BasePreferenceController} lists from Xml.
*/
public class PreferenceControllerListHelper {
private static final String TAG = "PrefCtrlListHelper";
/**
* Instantiates a list of controller based on xml definition.
*/
@NonNull
public static List getPreferenceControllersFromXml(Context context,
@XmlRes int xmlResId) {
final List controllers = new ArrayList<>();
List preferenceMetadata;
try {
preferenceMetadata = PreferenceXmlParserUtils.extractMetadata(context, xmlResId,
MetadataFlag.FLAG_NEED_KEY | MetadataFlag.FLAG_NEED_PREF_CONTROLLER
| MetadataFlag.FLAG_INCLUDE_PREF_SCREEN);
} catch (IOException | XmlPullParserException e) {
Log.e(TAG, "Failed to parse preference xml for getting controllers", e);
return controllers;
}
for (Bundle metadata : preferenceMetadata) {
final String controllerName = metadata.getString(METADATA_CONTROLLER);
if (TextUtils.isEmpty(controllerName)) {
continue;
}
BasePreferenceController controller;
try {
controller = BasePreferenceController.createInstance(context, controllerName);
} catch (IllegalStateException e) {
Log.d(TAG, "Could not find Context-only controller for pref: " + controllerName);
final String key = metadata.getString(METADATA_KEY);
if (TextUtils.isEmpty(key)) {
Log.w(TAG, "Controller requires key but it's not defined in xml: "
+ controllerName);
continue;
}
try {
controller = BasePreferenceController.createInstance(context, controllerName,
key);
} catch (IllegalStateException e2) {
Log.w(TAG, "Cannot instantiate controller from reflection: " + controllerName);
continue;
}
}
controllers.add(controller);
}
return controllers;
}
/**
* Return a sub list of {@link AbstractPreferenceController} to only contain controller that
* doesn't exist in filter.
*
* @param filter The filter. This list will be unchanged.
* @param input This list will be filtered into a sublist and element is kept
* IFF the controller key is not used by anything from {@param filter}.
*/
@NonNull
public static List filterControllers(
@NonNull List input,
List filter) {
if (input == null || filter == null) {
return input;
}
final Set keys = new TreeSet<>();
final List filteredList = new ArrayList<>();
for (AbstractPreferenceController controller : filter) {
final String key = controller.getPreferenceKey();
if (key != null) {
keys.add(key);
}
}
for (BasePreferenceController controller : input) {
if (keys.contains(controller.getPreferenceKey())) {
Log.w(TAG, controller.getPreferenceKey() + " already has a controller");
continue;
}
filteredList.add(controller);
}
return filteredList;
}
}
然后会调用onCreatePreferences方法,再调用refreshAllPreferences。
/**
* Refresh all preference items, including both static prefs from xml, and dynamic items from
* DashboardCategory.
*/
private void refreshAllPreferences(final String TAG) {
final PreferenceScreen screen = getPreferenceScreen();
// First remove old preferences.
if (screen != null) {
// Intentionally do not cache PreferenceScreen because it will be recreated later.
screen.removeAll();
}
// Add resource based tiles.
displayResourceTiles();
refreshDashboardTiles(TAG);
final Activity activity = getActivity();
if (activity != null) {
Log.d(TAG, "All preferences added, reporting fully drawn");
activity.reportFullyDrawn();
}
updatePreferenceVisibility(mPreferenceControllers);
}
通过displayResourceTiles方法加载资源文件,但是getPreferenceScreenResId是一个抽象方法,具体实现在子类中,加载子类的资源文件。
@Override
protected abstract int getPreferenceScreenResId();
.................................................................................
private void displayResourceTiles() {
final int resId = getPreferenceScreenResId();
if (resId <= 0) {
return;
}
addPreferencesFromResource(resId);
final PreferenceScreen screen = getPreferenceScreen();
screen.setOnExpandButtonClickListener(this);
mPreferenceControllers.values().stream().flatMap(Collection::stream).forEach(
controller -> controller.displayPreference(screen));
}
遍历controllers并且调用controller的displayPreference方法。所有的controller都基础自BasePreferenceController.java。
/packages/apps/Settings/src/com/android/settings/core/BasePreferenceController.java
package com.android.settings.core;
import android.annotation.IntDef;
import android.content.Context;
import android.text.TextUtils;
import android.util.Log;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
import com.android.settings.search.SearchIndexableRaw;
import com.android.settings.slices.SliceData;
import com.android.settings.slices.Sliceable;
import com.android.settingslib.core.AbstractPreferenceController;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.List;
/**
* Abstract class to consolidate utility between preference controllers and act as an interface
* for Slices. The abstract classes that inherit from this class will act as the direct interfaces
* for each type when plugging into Slices.
*/
public abstract class BasePreferenceController extends AbstractPreferenceController implements
Sliceable {
private static final String TAG = "SettingsPrefController";
/**
* Denotes the availability of the Setting.
*
* Used both explicitly and by the convenience methods {@link #isAvailable()} and
* {@link #isSupported()}.
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef({AVAILABLE, AVAILABLE_UNSEARCHABLE, UNSUPPORTED_ON_DEVICE, DISABLED_FOR_USER,
DISABLED_DEPENDENT_SETTING, CONDITIONALLY_UNAVAILABLE})
public @interface AvailabilityStatus {
}
/**
* The setting is available, and searchable to all search clients.
*/
public static final int AVAILABLE = 0;
/**
* The setting is available, but is not searchable to any search client.
*/
public static final int AVAILABLE_UNSEARCHABLE = 1;
/**
* A generic catch for settings which are currently unavailable, but may become available in
* the future. You should use {@link #DISABLED_FOR_USER} or {@link #DISABLED_DEPENDENT_SETTING}
* if they describe the condition more accurately.
*/
public static final int CONDITIONALLY_UNAVAILABLE = 2;
/**
* The setting is not, and will not supported by this device.
*
* There is no guarantee that the setting page exists, and any links to the Setting should take
* you to the home page of Settings.
*/
public static final int UNSUPPORTED_ON_DEVICE = 3;
/**
* The setting cannot be changed by the current user.
*
* Links to the Setting should take you to the page of the Setting, even if it cannot be
* changed.
*/
public static final int DISABLED_FOR_USER = 4;
/**
* The setting has a dependency in the Settings App which is currently blocking access.
*
* It must be possible for the Setting to be enabled by changing the configuration of the device
* settings. That is, a setting that cannot be changed because of the state of another setting.
* This should not be used for a setting that would be hidden from the UI entirely.
*
* Correct use: Intensity of night display should be {@link #DISABLED_DEPENDENT_SETTING} when
* night display is off.
* Incorrect use: Mobile Data is {@link #DISABLED_DEPENDENT_SETTING} when there is no
* data-enabled sim.
*
* Links to the Setting should take you to the page of the Setting, even if it cannot be
* changed.
*/
public static final int DISABLED_DEPENDENT_SETTING = 5;
protected final String mPreferenceKey;
protected UiBlockListener mUiBlockListener;
/**
* Instantiate a controller as specified controller type and user-defined key.
*
* This is done through reflection. Do not use this method unless you know what you are doing.
*/
public static BasePreferenceController createInstance(Context context,
String controllerName, String key) {
try {
final Class> clazz = Class.forName(controllerName);
final Constructor> preferenceConstructor =
clazz.getConstructor(Context.class, String.class);
final Object[] params = new Object[]{context, key};
return (BasePreferenceController) preferenceConstructor.newInstance(params);
} catch (ClassNotFoundException | NoSuchMethodException | InstantiationException |
IllegalArgumentException | InvocationTargetException | IllegalAccessException e) {
throw new IllegalStateException(
"Invalid preference controller: " + controllerName, e);
}
}
/**
* Instantiate a controller as specified controller type.
*
* This is done through reflection. Do not use this method unless you know what you are doing.
*/
public static BasePreferenceController createInstance(Context context, String controllerName) {
try {
final Class> clazz = Class.forName(controllerName);
final Constructor> preferenceConstructor = clazz.getConstructor(Context.class);
final Object[] params = new Object[]{context};
return (BasePreferenceController) preferenceConstructor.newInstance(params);
} catch (ClassNotFoundException | NoSuchMethodException | InstantiationException |
IllegalArgumentException | InvocationTargetException | IllegalAccessException e) {
throw new IllegalStateException(
"Invalid preference controller: " + controllerName, e);
}
}
public BasePreferenceController(Context context, String preferenceKey) {
super(context);
mPreferenceKey = preferenceKey;
if (TextUtils.isEmpty(mPreferenceKey)) {
throw new IllegalArgumentException("Preference key must be set");
}
}
/**
* @return {@AvailabilityStatus} for the Setting. This status is used to determine if the
* Setting should be shown or disabled in Settings. Further, it can be used to produce
* appropriate error / warning Slice in the case of unavailability.
*
* The status is used for the convenience methods: {@link #isAvailable()},
* {@link #isSupported()}
*/
@AvailabilityStatus
public abstract int getAvailabilityStatus();
@Override
public String getPreferenceKey() {
return mPreferenceKey;
}
/**
* @return {@code true} when the controller can be changed on the device.
*
*
* Will return true for {@link #AVAILABLE} and {@link #DISABLED_DEPENDENT_SETTING}.
*
* When the availability status returned by {@link #getAvailabilityStatus()} is
* {@link #DISABLED_DEPENDENT_SETTING}, then the setting will be disabled by default in the
* DashboardFragment, and it is up to the {@link BasePreferenceController} to enable the
* preference at the right time.
*
* TODO (mfritze) Build a dependency mechanism to allow a controller to easily define the
* dependent setting.
*/
@Override
public final boolean isAvailable() {
final int availabilityStatus = getAvailabilityStatus();
return (availabilityStatus == AVAILABLE
|| availabilityStatus == AVAILABLE_UNSEARCHABLE
|| availabilityStatus == DISABLED_DEPENDENT_SETTING);
}
/**
* @return {@code false} if the setting is not applicable to the device. This covers both
* settings which were only introduced in future versions of android, or settings that have
* hardware dependencies.
*
* Note that a return value of {@code true} does not mean that the setting is available.
*/
public final boolean isSupported() {
return getAvailabilityStatus() != UNSUPPORTED_ON_DEVICE;
}
/**
* Displays preference in this controller.
*/
@Override
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
if (getAvailabilityStatus() == DISABLED_DEPENDENT_SETTING) {
// Disable preference if it depends on another setting.
final Preference preference = screen.findPreference(getPreferenceKey());
if (preference != null) {
preference.setEnabled(false);
}
}
}
/**
* @return the UI type supported by the controller.
*/
@SliceData.SliceType
public int getSliceType() {
return SliceData.SliceType.INTENT;
}
/**
* Updates non-indexable keys for search provider.
*
* Called by SearchIndexProvider#getNonIndexableKeys
*/
public void updateNonIndexableKeys(List keys) {
final boolean shouldSuppressFromSearch = !isAvailable()
|| getAvailabilityStatus() == AVAILABLE_UNSEARCHABLE;
if (shouldSuppressFromSearch) {
final String key = getPreferenceKey();
if (TextUtils.isEmpty(key)) {
Log.w(TAG, "Skipping updateNonIndexableKeys due to empty key " + toString());
return;
}
if (keys.contains(key)) {
Log.w(TAG, "Skipping updateNonIndexableKeys, key already in list. " + toString());
return;
}
keys.add(key);
}
}
/**
* Updates raw data for search provider.
*
* Called by SearchIndexProvider#getRawDataToIndex
*/
public void updateRawDataToIndex(List rawData) {
}
/**
* Set {@link UiBlockListener}
*
* @param uiBlockListener listener to set
*/
public void setUiBlockListener(UiBlockListener uiBlockListener) {
mUiBlockListener = uiBlockListener;
}
/**
* Listener to invoke when background job is finished
*/
public interface UiBlockListener {
/**
* To notify client that UI related background work is finished.
* (i.e. Slice is fully loaded.)
*
* @param controller Controller that contains background work
*/
void onBlockerWorkFinished(BasePreferenceController controller);
}
/**
* Used for {@link BasePreferenceController} to decide whether it is ui blocker.
* If it is, entire UI will be invisible for a certain period until controller
* invokes {@link UiBlockListener}
*
* This won't block UI thread however has similar side effect. Please use it if you
* want to avoid janky animation(i.e. new preference is added in the middle of page).
*
* This music be used in {@link BasePreferenceController}
*/
public interface UiBlocker {
}
}
而BasePreferenceController 继承自 AbstractPreferenceController
package com.android.settingslib.core;
import android.content.Context;
import android.text.TextUtils;
import android.util.Log;
import androidx.preference.Preference;
import androidx.preference.PreferenceGroup;
import androidx.preference.PreferenceScreen;
/**
* A controller that manages event for preference.
*/
public abstract class AbstractPreferenceController {
private static final String TAG = "AbstractPrefController";
protected final Context mContext;
public AbstractPreferenceController(Context context) {
mContext = context;
}
/**
* Displays preference in this controller.
*/
public void displayPreference(PreferenceScreen screen) {
final String prefKey = getPreferenceKey();
if (TextUtils.isEmpty(prefKey)) {
Log.w(TAG, "Skipping displayPreference because key is empty:" + getClass().getName());
return;
}
if (isAvailable()) {
setVisible(screen, prefKey, true /* visible */);
if (this instanceof Preference.OnPreferenceChangeListener) {
final Preference preference = screen.findPreference(prefKey);
preference.setOnPreferenceChangeListener(
(Preference.OnPreferenceChangeListener) this);
}
} else {
setVisible(screen, prefKey, false /* visible */);
}
}
/**
* Updates the current status of preference (summary, switch state, etc)
*/
public void updateState(Preference preference) {
refreshSummary(preference);
}
/**
* Refresh preference summary with getSummary()
*/
protected void refreshSummary(Preference preference) {
if (preference == null) {
return;
}
final CharSequence summary = getSummary();
if (summary == null) {
// Default getSummary returns null. If subclass didn't override this, there is nothing
// we need to do.
return;
}
preference.setSummary(summary);
}
/**
* Returns true if preference is available (should be displayed)
*/
public abstract boolean isAvailable();
/**
* Handles preference tree click
*
* @param preference the preference being clicked
* @return true if click is handled
*/
public boolean handlePreferenceTreeClick(Preference preference) {
return false;
}
/**
* Returns the key for this preference.
*/
public abstract String getPreferenceKey();
/**
* Show/hide a preference.
*/
protected final void setVisible(PreferenceGroup group, String key, boolean isVisible) {
final Preference pref = group.findPreference(key);
if (pref != null) {
pref.setVisible(isVisible);
}
}
/**
* @return a {@link CharSequence} for the summary of the preference.
*/
public CharSequence getSummary() {
return null;
}
}
AbstractPreferenceController类中的displayPreference方法调用了getPreferenceKey方法,该方法是个抽象类,具体在子类BasePreferenceController中实现,返回mPreferenceKey。而这个mPreferenceKey就是资源文件xml中的android:key属性。根据这个熟悉去设置preference是否可见。