Settings源码在\packages\apps\Settings目录下(vendor下可能也有oem定制的设置)。
查看AndroidManifest.xml可以看出Settings的主Activity是Settings,但是进入Settings.java发现里面只是声明了一些Activity,所有的Activity都继承SettingsActivity。
<activity android:name="Settings"
android:taskAffinity="com.android.settings.root"
android:label="@string/settings_label_launcher"
android:launchMode="singleTask">
<intent-filter android:priority="1">
<action android:name="android.settings.SETTINGS" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<meta-data android:name="com.android.settings.PRIMARY_PROFILE_CONTROLLED"
android:value="true" />
</activity>
<!-- Alias for launcher activity only, as this belongs to each profile. -->
<activity-alias android:name="Settings"
android:taskAffinity="com.android.settings.root"
android:label="@string/settings_label_launcher"
android:launchMode="singleTask"
android:targetActivity="Settings">
<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>
一、onCreate方法
先看下onCreate方法,通过savedState判断是否之前有保存任务,通过initialFragmentName判断是否有想要进入的Fragment,点击设置图标进入设置一级目录initialFragmentName为null,否则表示进入二级或者多级目录。
@Override
protected void onCreate(Bundle savedState) {
super.onCreate(savedState);
……
// Getting Intent properties can only be done after the super.onCreate(...)
final String initialFragmentName = intent.getStringExtra(EXTRA_SHOW_FRAGMENT);
Log.d(LOG_TAG, "onCreate initialFragmentName="+initialFragmentName);
final ComponentName cn = intent.getComponent();
final String className = cn.getClassName();
mIsShowingDashboard = className.equals(Settings.class.getName());
// This is a "Sub Settings" when:
// - this is a real SubSettings
// - or :settings:show_fragment_as_subsetting is passed to the Intent
final boolean isSubSettings = this instanceof SubSettings ||
intent.getBooleanExtra(EXTRA_SHOW_FRAGMENT_AS_SUBSETTING, false);
// If this is a sub settings, then apply the SubSettings Theme for the ActionBar content
// insets
if (isSubSettings) {
setTheme(R.style.Theme_SubSettings);
}
setContentView(mIsShowingDashboard ?
R.layout.settings_main_dashboard : R.layout.settings_main_prefs);
mContent = findViewById(R.id.main_content);
getFragmentManager().addOnBackStackChangedListener(this);
Log.d(LOG_TAG, "onCreate savedState="+savedState);
if (savedState != null) {
// We are restarting from a previous saved state; used that to initialize, instead
// of starting fresh.
setTitleFromIntent(intent);
ArrayList<DashboardCategory> categories =
savedState.getParcelableArrayList(SAVE_KEY_CATEGORIES);
if (categories != null) {
mCategories.clear();
mCategories.addAll(categories);
setTitleFromBackStack();
}
} else {
launchSettingFragment(initialFragmentName, isSubSettings, intent);
}
……
}
然后执行launchSettingFragment跳转到相应的Fragment或者Preference。
二、onResume方法
onResume比较简洁,mDevelopmentSettingsListener是监听是否打开开发者模式的本地广播,以及更新Title。
@Override
protected void onResume() {
super.onResume();
mDevelopmentSettingsListener = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
updateTilesList();
}
};
LocalBroadcastManager.getInstance(this).registerReceiver(mDevelopmentSettingsListener,
new IntentFilter(DevelopmentSettingsEnabler.DEVELOPMENT_SETTINGS_CHANGED_ACTION));
registerReceiver(mBatteryInfoReceiver, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
updateTilesList();
updateDeviceIndex();
}
updateTilesList使用AsyncTask去判断title是否有变化,通过somethingChanged表示是否有变化,分别判断了wifi、蓝牙、设备连接、sim设置、电量、应用数据使用、时间、网络等是否变化,他们直接用或的关系连接。
private void updateTilesList() {
// Generally the items that are will be changing from these updates will
// not be in the top list of tiles, so run it in the background and the
// SettingsDrawerActivity will pick up on the updates automatically.
AsyncTask.execute(new Runnable() {
@Override
public void run() {
doUpdateTilesList();
}
});
}
private void doUpdateTilesList() {
PackageManager pm = getPackageManager();
final UserManager um = UserManager.get(this);
final boolean isAdmin = um.isAdminUser();
final FeatureFactory featureFactory = FeatureFactory.getFactory(this);
boolean somethingChanged = false;
final String packageName = getPackageName();
final StringBuilder changedList = new StringBuilder();
somethingChanged = setTileEnabled(changedList,
new ComponentName(packageName, WifiSettingsActivity.class.getName()),
pm.hasSystemFeature(PackageManager.FEATURE_WIFI), isAdmin) || somethingChanged;
somethingChanged = setTileEnabled(changedList, new ComponentName(packageName,
Settings.BluetoothSettingsActivity.class.getName()),
pm.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH), isAdmin)
|| somethingChanged;
// Enable DataUsageSummaryActivity if the data plan feature flag is turned on otherwise
// enable DataPlanUsageSummaryActivity.
somethingChanged = setTileEnabled(changedList,
new ComponentName(packageName, Settings.DataUsageSummaryActivity.class.getName()),
Utils.isBandwidthControlEnabled() /* enabled */,
isAdmin) || somethingChanged;
somethingChanged = setTileEnabled(changedList,
new ComponentName(packageName,
Settings.ConnectedDeviceDashboardActivity.class.getName()),
!UserManager.isDeviceInDemoMode(this) /* enabled */,
isAdmin) || somethingChanged;
somethingChanged = setTileEnabled(changedList, new ComponentName(packageName,
Settings.SimSettingsActivity.class.getName()),
Utils.showSimCardTile(this), isAdmin)
|| somethingChanged;
somethingChanged = setTileEnabled(changedList, new ComponentName(packageName,
Settings.PowerUsageSummaryActivity.class.getName()),
mBatteryPresent, isAdmin) || somethingChanged;
final boolean isDataUsageSettingsV2Enabled =
FeatureFlagUtils.isEnabled(this, FeatureFlags.DATA_USAGE_SETTINGS_V2);
// Enable new data usage page if v2 enabled
somethingChanged = setTileEnabled(changedList, new ComponentName(packageName,
Settings.DataUsageSummaryActivity.class.getName()),
Utils.isBandwidthControlEnabled() && isDataUsageSettingsV2Enabled, isAdmin)
|| somethingChanged;
// Enable legacy data usage page if v2 disabled
somethingChanged = setTileEnabled(changedList, new ComponentName(packageName,
Settings.DataUsageSummaryLegacyActivity.class.getName()),
Utils.isBandwidthControlEnabled() && !isDataUsageSettingsV2Enabled, isAdmin)
|| somethingChanged;
somethingChanged = setTileEnabled(changedList, new ComponentName(packageName,
Settings.UserSettingsActivity.class.getName()),
UserHandle.MU_ENABLED && UserManager.supportsMultipleUsers()
&& !Utils.isMonkeyRunning(), isAdmin)
|| somethingChanged;
somethingChanged = setTileEnabled(changedList, new ComponentName(packageName,
Settings.NetworkDashboardActivity.class.getName()),
!UserManager.isDeviceInDemoMode(this), isAdmin)
|| somethingChanged;
somethingChanged = setTileEnabled(changedList, new ComponentName(packageName,
Settings.DateTimeSettingsActivity.class.getName()),
!UserManager.isDeviceInDemoMode(this), isAdmin)
|| somethingChanged;
final boolean showDev = DevelopmentSettingsEnabler.isDevelopmentSettingsEnabled(this)
&& !Utils.isMonkeyRunning();
final boolean isAdminOrDemo = um.isAdminUser() || um.isDemoUser();
somethingChanged = setTileEnabled(changedList, new ComponentName(packageName,
Settings.DevelopmentSettingsDashboardActivity.class.getName()),
showDev, isAdminOrDemo)
|| somethingChanged;
// Enable/disable backup settings depending on whether the user is admin.
somethingChanged = setTileEnabled(changedList, new ComponentName(packageName,
BackupSettingsActivity.class.getName()), true, isAdmin)
|| somethingChanged;
somethingChanged = setTileEnabled(changedList, new ComponentName(packageName,
Settings.WifiDisplaySettingsActivity.class.getName()),
WifiDisplaySettings.isAvailable(this), isAdmin)
|| somethingChanged;
// Enable/disable the Me Card page.
final boolean aboutPhoneV2Enabled = featureFactory
.getAccountFeatureProvider()
.isAboutPhoneV2Enabled(this);
somethingChanged = setTileEnabled(changedList, new ComponentName(packageName,
Settings.MyDeviceInfoActivity.class.getName()),
aboutPhoneV2Enabled, isAdmin)
|| somethingChanged;
somethingChanged = setTileEnabled(changedList, new ComponentName(packageName,
Settings.DeviceInfoSettingsActivity.class.getName()),
!aboutPhoneV2Enabled, isAdmin)
|| somethingChanged;
if (UserHandle.MU_ENABLED && !isAdmin) {
// When on restricted users, disable all extra categories (but only the settings ones).
final List<DashboardCategory> categories = mDashboardFeatureProvider.getAllCategories();
synchronized (categories) {
for (DashboardCategory category : categories) {
final int tileCount = category.getTilesCount();
for (int i = 0; i < tileCount; i++) {
final ComponentName component = category.getTile(i).intent.getComponent();
final String name = component.getClassName();
final boolean isEnabledForRestricted = ArrayUtils.contains(
SettingsGateway.SETTINGS_FOR_RESTRICTED, name) || (isAdminOrDemo
&& Settings.DevelopmentSettingsDashboardActivity.class.getName()
.equals(name));
if (packageName.equals(component.getPackageName())
&& !isEnabledForRestricted) {
somethingChanged =
setTileEnabled(changedList, component, false, isAdmin)
|| somethingChanged;
}
}
}
}
}
// Final step, refresh categories.
if (somethingChanged) {
Log.d(LOG_TAG, "Enabled state changed for some tiles, reloading all categories "
+ changedList.toString());
updateCategories();
} else {
Log.d(LOG_TAG, "No enabled state changed, skipping updateCategory call");
}
}
如果somethingChanged为true,则调用updateCategories方法刷新目录,updateCategories方法定义在SettingsActivity的父类SettingsDrawerActivity.java中SettingsDrawerActivity.java在frameworks\base\packages\SettingsLib\src\com\android\settingslib\drawer目录下。updateCategories又执行了一个AsyncTask。
public void updateCategories() {
new CategoriesUpdateTask().execute();
}
private class CategoriesUpdateTask extends AsyncTask<Void, Void, Void> {
private final CategoryManager mCategoryManager;
public CategoriesUpdateTask() {
mCategoryManager = CategoryManager.get(SettingsDrawerActivity.this);
}
@Override
protected Void doInBackground(Void... params) {
mCategoryManager.reloadAllCategories(SettingsDrawerActivity.this, getSettingPkg());
return null;
}
@Override
protected void onPostExecute(Void result) {
mCategoryManager.updateCategoryFromBlacklist(sTileBlacklist);
onCategoriesChanged();
}
}
重点看下CategoriesUpdateTask的doInBackground中的后台任务reloadAllCategories,reloadAllCategories又调用了tryInitCategories方法,主要的目录信息是从TileUtils的getCategories得到,然后进行排序过滤得到最终的目录列表。
public synchronized void reloadAllCategories(Context context, String settingPkg) {
final boolean forceClearCache = mInterestingConfigChanges.applyNewConfig(
context.getResources());
mCategories = null;
tryInitCategories(context, forceClearCache, settingPkg);
}
TileUtils的getCategories方法通过getTilesForAction获得tile列表,getTilesForIntent方法中先通过PackageManager的queryIntentActivitiesAsUser方法获取到所有的应用,然后通过resolved.system对非系统app进行初始过滤,然后判断activityInfo.metaData是否包含"com.android.settings.category",如果包含则将这个应用或者action添加到列表中。
public static void getTilesForIntent(
Context context, UserHandle user, Intent intent,
Map<Pair<String, String>, Tile> addedCache, String defaultCategory, List<Tile> outTiles,
boolean usePriority, boolean checkCategory, boolean forceTintExternalIcon,
boolean shouldUpdateTiles) {
PackageManager pm = context.getPackageManager();
List<ResolveInfo> results = pm.queryIntentActivitiesAsUser(intent,
PackageManager.GET_META_DATA, user.getIdentifier());
Map<String, IContentProvider> providerMap = new HashMap<>();
for (ResolveInfo resolved : results) {
if (!resolved.system) {
// Do not allow any app to add to settings, only system ones.
continue;
}
ActivityInfo activityInfo = resolved.activityInfo;
Bundle metaData = activityInfo.metaData;
String categoryKey = defaultCategory;
// Load category
if (checkCategory && ((metaData == null) || !metaData.containsKey(EXTRA_CATEGORY_KEY))
&& categoryKey == null) {
Log.e(LOG_TAG, "Found " + resolved.activityInfo.name + " for intent "
+ intent + " missing metadata "
+ (metaData == null ? "" : EXTRA_CATEGORY_KEY));
continue;
} else {
categoryKey = metaData.getString(EXTRA_CATEGORY_KEY);
Log.e(LOG_TAG, "name: " + resolved.activityInfo.name + ", categoryKey:"+categoryKey);
}
Pair<String, String> key = new Pair<String, String>(activityInfo.packageName,
activityInfo.name);
Tile tile = addedCache.get(key);
if (tile == null) {
tile = new Tile();
tile.intent = new Intent().setClassName(
activityInfo.packageName, activityInfo.name);
tile.category = categoryKey;
tile.priority = usePriority ? resolved.priority : 0;
tile.metaData = activityInfo.metaData;
updateTileData(context, tile, activityInfo, activityInfo.applicationInfo,
pm, providerMap, forceTintExternalIcon);
if (DEBUG) Log.d(LOG_TAG, "Adding tile " + tile.title);
addedCache.put(key, tile);
} else if (shouldUpdateTiles) {
updateSummaryAndTitle(context, providerMap, tile);
}
if (!tile.userHandle.contains(user)) {
tile.userHandle.add(user);
}
if (!outTiles.contains(tile)) {
outTiles.add(tile);
}
}
}
三、第三方系统app添加到设置
第二部分的最后提到metaData包含"com.android.settings.category"的系统app可以加入到设置中,那么理论上可以通过在AndroidManifest.xml中配置activity的meta-data属性为"com.android.settings.category"然后在源码编译下即可添加到设置。
其实还有一个限制,此限制也是指定第三方app在设置中的位置(是在一级目录还是二级目录),可以查看frameworks\base\packages\SettingsLib\src\com\android\settingslib\drawer\CategoryKey.java的实例key。
展示实例
<intent-filter android:priority="0">
<action android:name="com.android.settings.action.SETTINGS" />
</intent-filter>
<meta-data
android:name="com.android.settings.category"
android:value="com.android.settings.category.ia.homepage" />
<meta-data
android:name="com.android.settings.title"
android:value="@string/app_name" />
<meta-data
android:name="com.android.settings.summary"
android:value="@string/app_name" />