android framework13-settings【0

1.概述

简单学习下系统设置功能,这个app在packages/apps/Settings目录下,app里边引用了一些库可能在frameworks/base/packages/SettingsLib或SettingsProvider目录下

2.Fragment的层级关系

因为设置里都是选项卡,所以基类用的就是PreferenceFragmentCompat,这里看下代码里自定义的Fragment

2.1 ObservablePreferenceFragment

添加生命周期监听的逻辑

public abstract class ObservablePreferenceFragment extends PreferenceFragmentCompat
        implements LifecycleOwner {

    private final Lifecycle mLifecycle = new Lifecycle(this);

    public Lifecycle getSettingsLifecycle() {
        return mLifecycle;
    }

2.2 InstrumentedPreferenceFragment

Instrumented fragment that logs visibility state.

public abstract class InstrumentedPreferenceFragment extends ObservablePreferenceFragment
        implements Instrumentable {
        
        
    //注意一下,下边的方法后边又被DashboardFragment重写了,
    public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
        final int resId = getPreferenceScreenResId();//布局通过这个方法设置
        if (resId > 0) {
            addPreferencesFromResource(resId);
        }
    }

    @Override
    public void addPreferencesFromResource(@XmlRes int preferencesResId) {
        super.addPreferencesFromResource(preferencesResId);
        updateActivityTitleWithScreenTitle(getPreferenceScreen());//更新activity的title
    }
    
    private void updateActivityTitleWithScreenTitle(PreferenceScreen screen) {
        if (screen != null) {
            final CharSequence title = screen.getTitle();
            if (!TextUtils.isEmpty(title)) {
                getActivity().setTitle(title);
            } else {
                Log.w(TAG, "Screen title missing for fragment " + this.getClass().getName());
            }
        }
    }    

2.3 SettingsPreferenceFragment

settings fragment的基类,带有一些辅助函数和对话框管理

public abstract class SettingsPreferenceFragment extends InstrumentedPreferenceFragment
        implements DialogCreatable, HelpResourceProvider, Indexable {
        

    public void addPreferencesFromResource(@XmlRes int preferencesResId) {
        super.addPreferencesFromResource(preferencesResId);
        checkAvailablePrefs(getPreferenceScreen());
    }

    //选项的可见性设置,xml里的选项不见得都是可见的
    void checkAvailablePrefs(PreferenceGroup preferenceGroup) {
        if (preferenceGroup == null) return;
        for (int i = 0; i < preferenceGroup.getPreferenceCount(); i++) {
            Preference pref = preferenceGroup.getPreference(i);
            if (pref instanceof SelfAvailablePreference
                    && !((SelfAvailablePreference) pref).isAvailable(getContext())) {
                pref.setVisible(false);
            } else if (pref instanceof PreferenceGroup) {
                checkAvailablePrefs((PreferenceGroup) pref);
            }
        }
    }

>showDialog

这个类里封装了如下显示一个dialog的逻辑

使用的时候调用showDialog传递一个int值,完事覆写OnCreateDialog里根据dialogId返回不同的Dialog即可。另外,cancel,dismiss的监听方法也都有,还有removeDialog的方法也有

    protected void showDialog(int dialogId) {
        if (mDialogFragment != null) {
            Log.e(TAG, "Old dialog fragment not null!");
        }
        mDialogFragment = SettingsDialogFragment.newInstance(this, dialogId);
        mDialogFragment.show(getChildFragmentManager(), Integer.toString(dialogId));
    }

    @Override
    public Dialog onCreateDialog(int dialogId) {
        return null;
    }
    
    protected void removeDialog(int dialogId) {
        if (mDialogFragment != null && mDialogFragment.getDialogId() == dialogId) {
            mDialogFragment.dismissAllowingStateLoss();
        }
        mDialogFragment = null;
    }

    protected void setOnCancelListener(DialogInterface.OnCancelListener listener) {
        if (mDialogFragment != null) {
            mDialogFragment.mOnCancelListener = listener;
        }
    }
    protected void setOnDismissListener(DialogInterface.OnDismissListener listener) {
        if (mDialogFragment != null) {
            mDialogFragment.mOnDismissListener = listener;
        }
    }    

2.4 DashboardFragment

Base fragment for dashboard style UI containing a list of static and dynamic setting items.

public abstract class DashboardFragment extends SettingsPreferenceFragment
        implements CategoryListener, Indexable, PreferenceGroup.OnExpandButtonClickListener,
        BasePreferenceController.UiBlockListener {
        
    public void onAttach(Context context) {
        super.onAttach(context);
        //...
        //代码里获取controllers
        final List<AbstractPreferenceController> controllersFromCode =
                createPreferenceControllers(context);
        //从xml里解析获取controllers
        final List<BasePreferenceController> controllersFromXml = PreferenceControllerListHelper
                .getPreferenceControllersFromXml(context, getPreferenceScreenResId());
        // Filter xml-based controllers in case a similar controller is created from code already.
        //根据key值,假设controllersFromXml为[A B C],controllersFromCode为[B D],结果为[A C]
        final List<BasePreferenceController> uniqueControllerFromXml =
                PreferenceControllerListHelper.filterControllers(
                        controllersFromXml, controllersFromCode);
//从下边addAll的逻辑可以看出,controllersFromCode里的controller优先级比较高
//controllersFromCode和controllersFromXml都有的controller,保留controllersFromCode里的
        // Add unique controllers to list.
        if (controllersFromCode != null) {
            mControllers.addAll(controllersFromCode);
        }
        mControllers.addAll(uniqueControllerFromXml);

        // And wire up with lifecycle.
        final Lifecycle lifecycle = getSettingsLifecycle();
        uniqueControllerFromXml.forEach(controller -> {
            if (controller instanceof LifecycleObserver) {
                lifecycle.addObserver((LifecycleObserver) controller);
            }
        });

        // Set metrics category for BasePreferenceController.
//...
        mPlaceholderPreferenceController =
                new DashboardTilePlaceholderPreferenceController(context);
                //添加一个占位符,方便动态添加选项
        mControllers.add(mPlaceholderPreferenceController);
        for (AbstractPreferenceController controller : mControllers) {
        //根据controller的class,把值保存在一个叫mPreferenceControllers的map里
            addPreferenceController(controller);
        }
    }


    public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
        checkUiBlocker(mControllers);
        refreshAllPreferences(getLogTag());
        mControllers.stream()
                .map(controller -> (Preference) findPreference(controller.getPreferenceKey()))
                .filter(Objects::nonNull)
                .forEach(preference -> {
                    // Give all controllers a chance to handle click.
                    preference.getExtras().putInt(CATEGORY, getMetricsCategory());
                });
    }
    
    //这个暂时不看了,搜了下继承UiBlock的目前就一个类BlockingSlicePrefController,蓝牙部分会用到
    void checkUiBlocker(List<AbstractPreferenceController> controllers) {
        final List<String> keys = new ArrayList<>();
        final List<BasePreferenceController> baseControllers = new ArrayList<>();
        controllers.forEach(controller -> {
            if (controller instanceof BasePreferenceController.UiBlocker
                    && controller.isAvailable()) {
                ((BasePreferenceController) controller).setUiBlockListener(this);
                keys.add(controller.getPreferenceKey());
                baseControllers.add((BasePreferenceController) controller);
            }
        });

        if (!keys.isEmpty()) {
            mBlockerController = new UiBlockerController(keys);
            mBlockerController.start(() -> {//延迟300ms后,在主线程执行下边的代码
                updatePreferenceVisibility(mPreferenceControllers);
                baseControllers.forEach(controller -> controller.setUiBlockerFinished(true));
            });
        }
    } 
    
    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();
        }

        // 这个就是从xml加载数据,具体见下边的方法,用到了addPreferencesFromResource()
        displayResourceTiles();

        refreshDashboardTiles(tag);//详见后续分析

        final Activity activity = getActivity();
        if (activity != null) {
            activity.reportFullyDrawn();
        }
    //更新选项的可见性。
        updatePreferenceVisibility(mPreferenceControllers);
    }
    
    private void displayResourceTiles() {
        final int resId = getPreferenceScreenResId();
        if (resId <= 0) {
            return;
        }
        addPreferencesFromResource(resId);
        final PreferenceScreen screen = getPreferenceScreen();
        screen.setOnExpandButtonClickListener(this);
        displayResourceTilesToScreen(screen);//这个就是处理选项的可见性
    }   
    

>updatePreferenceStates

更新选项的状态,主要是调用controller的updateState方法,默认实现是更新summary

    public void onResume() {
        super.onResume();
        updatePreferenceStates();
    }

循环所有的controller,根据key值找到对应的preference,找到的话就调用controller的updateState方法更新选项卡

    protected void updatePreferenceStates() {
        final PreferenceScreen screen = getPreferenceScreen();
        Collection<List<AbstractPreferenceController>> controllerLists =
                mPreferenceControllers.values();
        for (List<AbstractPreferenceController> controllerList : controllerLists) {
            for (AbstractPreferenceController controller : controllerList) {
                if (!controller.isAvailable()) {
                    continue;
                }

                final String key = controller.getPreferenceKey();
                if (TextUtils.isEmpty(key)) {
                    continue;
                }

                final Preference preference = screen.findPreference(key);
                if (preference == null) {
                    continue;
                }
                //核心是这个
                controller.updateState(preference);
            }
        }
    }

>AbstractPreferenceController.java

    public void displayPreference(PreferenceScreen screen) {
        final String prefKey = getPreferenceKey();
        if (TextUtils.isEmpty(prefKey)) {//没有key的不做处理
            return;
        }
        if (isAvailable()) {//根据方法的返回值决定这个选项是否可见
            setVisible(screen, prefKey, true /* visible */);
            if (this instanceof Preference.OnPreferenceChangeListener) {
                final Preference preference = screen.findPreference(prefKey);
                if (preference != null) {
                    preference.setOnPreferenceChangeListener(
                            (Preference.OnPreferenceChangeListener) this);
                }
            }
        } else {
            setVisible(screen, prefKey, false /* visible */);
        }
    }

>updatePreferenceVisibility()

这个方法有2个地方调用:

一个是checkUiBlocker方法,如下 keys不为null,表示有uiBlock的controller,那么会延迟300ms执行

        if (!keys.isEmpty()) {
            mBlockerController = new UiBlockerController(keys);
            mBlockerController.start(() -> {
                updatePreferenceVisibility(mPreferenceControllers);
                baseControllers.forEach(controller -> controller.setUiBlockerFinished(true));
            });
        }

另一个是在正常的onCreatePreferences > refreshAllPreferences里调用,这时候如果没有uiBlock的controller,那么这个方法里就会直接更新选项的可见性了。

>onPreferenceTreeClick()

选项的点击事件处理,可以看到交给controller来处理了,如果controller没做处理,那交给fragment自己处理

    public boolean onPreferenceTreeClick(Preference preference) {
        final Collection<List<AbstractPreferenceController>> controllers =
                mPreferenceControllers.values();
        for (List<AbstractPreferenceController> controllerList : controllers) {
            for (AbstractPreferenceController controller : controllerList) {
                if (controller.handlePreferenceTreeClick(preference)) {
                   //controller已经处理了,就返回
                    return true;
                }
            }
        }
        //controller未处理,使用默认的实现
        return super.onPreferenceTreeClick(preference);
    }

我们看下PreferenceFragmentCompat.java里默认的处理逻辑
首先判断回调的Fragment有没有实现OnPreferenceStartFragmentCallback,有的话用它来处理,没有的处理的话,继续判断activity有没有实现OnPreferenceStartFragmentCallback,有的话用它来处理,还是没有处理的话,那我们就自动给它跳转,用当前fragment的父容器来加载新的fragment


public Fragment getCallbackFragment() {
    return null;//注意,这里默认是null,自己实现的话一般返回this
}

public boolean onPreferenceTreeClick(Preference preference) {
//先判断有没有设置要跳转的fragment
    if (preference.getFragment() != null) {
        boolean handled = false;
        if (getCallbackFragment() instanceof OnPreferenceStartFragmentCallback) {
            handled = ((OnPreferenceStartFragmentCallback) getCallbackFragment())
                    .onPreferenceStartFragment(this, preference);
        }
        if (!handled && getActivity() instanceof OnPreferenceStartFragmentCallback) {
            handled = ((OnPreferenceStartFragmentCallback) getActivity())
                    .onPreferenceStartFragment(this, preference);
        }
        if (!handled) {

            final FragmentManager fragmentManager = requireActivity()
                    .getSupportFragmentManager();
            final Bundle args = preference.getExtras();
            final Fragment fragment = fragmentManager.getFragmentFactory().instantiate(
                    requireActivity().getClassLoader(), preference.getFragment());
            fragment.setArguments(args);
            fragment.setTargetFragment(this, 0);
            fragmentManager.beginTransaction()
                    .replace((((View) getView().getParent()).getId()), fragment)
                    .addToBackStack(null)
                    .commit();
        }
        return true;
    }
    return false;
}

>getCategoryKey

    public String getCategoryKey() {
        return DashboardFragmentRegistry.PARENT_TO_CATEGORY_KEY_MAP.get(getClass().getName());
    }

settings的首页是这个

        PARENT_TO_CATEGORY_KEY_MAP.put(TopLevelSettings.class.getName(),
                CategoryKey.CATEGORY_HOMEPAGE);
   public static final String CATEGORY_HOMEPAGE = "com.android.settings.category.ia.homepage";

>refreshDashboardTiles

根据categorykey查找对应的tile,动态添加选项

    private void refreshDashboardTiles(final String tag) {
        final PreferenceScreen screen = getPreferenceScreen();
    //数据获取逻辑参考 2.5到2.8小节
        final DashboardCategory category =
                mDashboardFeatureProvider.getTilesForCategory(getCategoryKey());
        if (category == null) {
            return;
        }
        final List<Tile> tiles = category.getTiles();
        if (tiles == null) {
            return;
        }
        // Create a list to track which tiles are to be removed.
        final Map<String, List<DynamicDataObserver>> remove = new ArrayMap(mDashboardTilePrefKeys);

        // Install dashboard tiles and collect pending observers.
        final boolean forceRoundedIcons = shouldForceRoundedIcon();
        final List<DynamicDataObserver> pendingObservers = new ArrayList<>();
        for (Tile tile : tiles) {
            final String key = mDashboardFeatureProvider.getDashboardKeyForTile(tile);
            if (TextUtils.isEmpty(key)) {
                continue;
            }
            
            if (!displayTile(tile)) {
                continue;
            }
            
            final List<DynamicDataObserver> observers;
            if (mDashboardTilePrefKeys.containsKey(key)) {
                // Have the key already, will rebind.
                final Preference preference = screen.findPreference(key);
                observers = mDashboardFeatureProvider.bindPreferenceToTileAndGetObservers(
                        getActivity(), this, forceRoundedIcons, preference, tile, key,
                        mPlaceholderPreferenceController.getOrder());
            } else {
                //根据tile创建对应的选项
                final Preference pref = createPreference(tile);
                //给选项卡绑定数据
                observers = mDashboardFeatureProvider.bindPreferenceToTileAndGetObservers(
                        getActivity(), this, forceRoundedIcons, pref, tile, key,
                        mPlaceholderPreferenceController.getOrder());
                screen.addPreference(pref);
                registerDynamicDataObservers(observers);
                mDashboardTilePrefKeys.put(key, observers);
            }
            if (observers != null) {
                pendingObservers.addAll(observers);
            }
            remove.remove(key);
        }

        // Remove tiles that are gone.
        for (Map.Entry<String, List<DynamicDataObserver>> entry : remove.entrySet()) {
            final String key = entry.getKey();
            mDashboardTilePrefKeys.remove(key);
            final Preference preference = screen.findPreference(key);
            if (preference != null) {
                screen.removePreference(preference);
            }
            unregisterDynamicDataObservers(entry.getValue());
        }

        // Wait for pending observers to update UI.
        if (!pendingObservers.isEmpty()) {
            final CountDownLatch mainLatch = new CountDownLatch(1);
            new Thread(() -> {
                pendingObservers.forEach(observer ->
                        awaitObserverLatch(observer.getCountDownLatch()));
                mainLatch.countDown();
            }).start();
            Log.d(tag, "Start waiting observers");
            awaitObserverLatch(mainLatch);
            Log.d(tag, "Stop waiting observers");
            pendingObservers.forEach(DynamicDataObserver::updateUi);
        }
    }

>createPreference

如果Tile是provider,那么返回switch选项,如果是activity,根据meta data里是否有switch的key,返回switch选项或者普通的选项

    protected Preference createPreference(Tile tile) {
        return tile instanceof ProviderTile
                ? new SwitchPreference(getPrefContext())
                : tile.hasSwitch()
                        ? new PrimarySwitchPreference(getPrefContext())
                        : new Preference(getPrefContext());
    }

>registerDynamicDataObservers

这里的observers就是选项卡的title,summary,switch的改变监听器,具体observer见2.5.1

    void registerDynamicDataObservers(List<DynamicDataObserver> observers) {
        if (observers == null || observers.isEmpty()) {
            return;
        }
        final ContentResolver resolver = getContentResolver();
        observers.forEach(observer -> registerDynamicDataObserver(resolver, observer));
    }

    private void registerDynamicDataObserver(ContentResolver resolver,
            DynamicDataObserver observer) {
        resolver.registerContentObserver(observer.getUri(), false, observer);
        mRegisteredObservers.add(observer);
    }

2.5.mDashboardFeatureProvider

>实例化

下边代码在上边的DashboardFragment里

        mDashboardFeatureProvider = FeatureFactory.getFactory(context).
                getDashboardFeatureProvider(context);

>FeatureFactory.java

    <!-- Fully-qualified class name for the implementation of the FeatureFactory to be instantiated. -->
    <string name="config_featureFactory" translatable="false">com.android.settings.overlay.FeatureFactoryImpl</string>

//

    public static FeatureFactory getFactory(Context context) {
        if (sFactory != null) {
            return sFactory;
        }
        final String clsName = context.getString(R.string.config_featureFactory);

        try {
            sFactory = (FeatureFactory) context.getClassLoader().loadClass(clsName).newInstance();
        } catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) {

        return sFactory;
    }

>FeatureFactoryImpl.java

这里就是用到的provider

    public DashboardFeatureProvider getDashboardFeatureProvider(Context context) {
        if (mDashboardFeatureProvider == null) {
            mDashboardFeatureProvider = new DashboardFeatureProviderImpl(
                    context.getApplicationContext());
        }
        return mDashboardFeatureProvider;
    }

2.5.1.DashboardFeatureProviderImpl

    public DashboardFeatureProviderImpl(Context context) {
        mContext = context.getApplicationContext();
        mCategoryManager = CategoryManager.get(context);
        mMetricsFeatureProvider = FeatureFactory.getFactory(context).getMetricsFeatureProvider();
        mPackageManager = context.getPackageManager();
    }

    @Override
    public DashboardCategory getTilesForCategory(String key) {
        return mCategoryManager.getTilesByCategory(mContext, key);
    }

>getDashboardKeyForTile

   private static final String DASHBOARD_TILE_PREF_KEY_PREFIX = "dashboard_tile_pref_";
   
    public String getDashboardKeyForTile(Tile tile) {
        if (tile == null) {
            return null;
        }
        if (tile.hasKey()) {
            return tile.getKey(mContext);
        }
        final StringBuilder sb = new StringBuilder(DASHBOARD_TILE_PREF_KEY_PREFIX);
        final ComponentName component = tile.getIntent().getComponent();
        sb.append(component.getClassName());
        return sb.toString();
    }

>bindPreferenceToTileAndGetObservers

    public List<DynamicDataObserver> bindPreferenceToTileAndGetObservers(FragmentActivity activity,
            DashboardFragment fragment, boolean forceRoundedIcon, Preference pref, Tile tile,
            String key, int baseOrder) {
        if (pref == null) {
            return null;
        }
        //给选项设置key值
        if (!TextUtils.isEmpty(key)) {
            pref.setKey(key);
        } else {
            pref.setKey(getDashboardKeyForTile(tile));
        }
        final List<DynamicDataObserver> outObservers = new ArrayList<>();
        //生成一个跟title有关的observer或者null
        DynamicDataObserver observer = bindTitleAndGetObserver(pref, tile);
        if (observer != null) {
            outObservers.add(observer);
        }
        //同理,这里是summary
        observer = bindSummaryAndGetObserver(pref, tile);
        if (observer != null) {
            outObservers.add(observer);
        }
        //选项开关的observer
        observer = bindSwitchAndGetObserver(pref, tile);
        if (observer != null) {
            outObservers.add(observer);
        }
        //给选项绑定图标
        bindIcon(pref, tile, forceRoundedIcon);
        //我们的tile是个activity
        if (tile instanceof ActivityTile) {
            final int sourceMetricsCategory = fragment.getMetricsCategory();
            final Bundle metadata = tile.getMetaData();
            String clsName = null;
            String action = null;
            //从meta data里获取数据
            if (metadata != null) {
            //获取要加载的fragment以及对应的action
                clsName = metadata.getString(SettingsActivity.META_DATA_KEY_FRAGMENT_CLASS);
                action = metadata.getString(META_DATA_KEY_INTENT_ACTION);
            }
            if (!TextUtils.isEmpty(clsName)) {
            //有class name,设置对应的fragment,默认回调里会自动跳转的
                pref.setFragment(clsName);
            } else {
            //不是fragment,设置选项对应的intent
                final Intent intent = new Intent(tile.getIntent());
                intent.putExtra(MetricsFeatureProvider.EXTRA_SOURCE_METRICS_CATEGORY,
                        sourceMetricsCategory);
                if (action != null) {
                    intent.setAction(action);
                }
                // Register the rule for injected apps.
                //当前fragment是首页那个fragment
                if (fragment instanceof TopLevelSettings) {
                    ActivityEmbeddingRulesController.registerTwoPanePairRuleForSettingsHome(
                            mContext,
                            new ComponentName(tile.getPackageName(), tile.getComponentName()),
                            action,
                            true /* clearTop */);
                }
                //设置点击事件,就是跳转intent
                pref.setOnPreferenceClickListener(preference -> {
                    TopLevelHighlightMixin highlightMixin = null;
                    boolean isDuplicateClick = false;
                    if (fragment instanceof TopLevelSettings
                            && ActivityEmbeddingUtils.isEmbeddingActivityEnabled(mContext)) {
                        // Highlight the preference whenever it's clicked
                        final TopLevelSettings topLevelSettings = (TopLevelSettings) fragment;
                        highlightMixin = topLevelSettings.getHighlightMixin();
                        isDuplicateClick = topLevelSettings.isDuplicateClick(preference);
                        topLevelSettings.setHighlightPreferenceKey(key);
                    }
                    launchIntentOrSelectProfile(activity, tile, intent, sourceMetricsCategory,
                            highlightMixin, isDuplicateClick);
                    return true;
                });
            }
        }
        //tile有order值的话,设置给选项卡
        if (tile.hasOrder()) {
            final String skipOffsetPackageName = activity.getPackageName();
            final int order = tile.getOrder();
            boolean shouldSkipBaseOrderOffset = TextUtils.equals(
                    skipOffsetPackageName, tile.getIntent().getComponent().getPackageName());
            if (shouldSkipBaseOrderOffset || baseOrder == Preference.DEFAULT_ORDER) {
                pref.setOrder(order);
            } else {
                pref.setOrder(order + baseOrder);
            }
        }
        return outObservers.isEmpty() ? null : outObservers;
    }

修改选项的标题

    private DynamicDataObserver bindTitleAndGetObserver(Preference preference, Tile tile) {
        final CharSequence title = tile.getTitle(mContext.getApplicationContext());
        if (title != null) {
        //设置title
            preference.setTitle(title);
            return null;
        }
        if (tile.getMetaData() != null && tile.getMetaData().containsKey(
                META_DATA_PREFERENCE_TITLE_URI)) {
                //给的uri,动态修改title,这里先给个默认值
            preference.setTitle(R.string.summary_placeholder);

            final Uri uri = TileUtils.getCompleteUri(tile, META_DATA_PREFERENCE_TITLE_URI,
                    METHOD_GET_DYNAMIC_TITLE);
            //返回一个observer动态修改title
            return createDynamicDataObserver(METHOD_GET_DYNAMIC_TITLE, uri, preference);
        }
        return null;
    }

2.6.CategoryManager.java

>getTilesByCategory

    public synchronized DashboardCategory getTilesByCategory(Context context, String categoryKey) {
        tryInitCategories(context);

        return mCategoryByKeyMap.get(categoryKey);
    }

>tryInitCategories

获取数据前都会调用这个方法,先初始化下数据

    private synchronized void tryInitCategories(Context context) {
        // Keep cached tiles by default. The cache is only invalidated when InterestingConfigChange
        // happens.
        tryInitCategories(context, false /* forceClearCache */);
    }

    private synchronized void tryInitCategories(Context context, boolean forceClearCache) {
        if (mCategories == null) {
            final boolean firstLoading = mCategoryByKeyMap.isEmpty();
            if (forceClearCache) {
                mTileByComponentCache.clear();
            }
            mCategoryByKeyMap.clear();
            mCategories = TileUtils.getCategories(context, mTileByComponentCache);
            for (DashboardCategory category : mCategories) {
                mCategoryByKeyMap.put(category.key, category);
            }
            backwardCompatCleanupForCategory(mTileByComponentCache, mCategoryByKeyMap);
            sortCategories(context, mCategoryByKeyMap);
            filterDuplicateTiles(mCategoryByKeyMap);
            if (firstLoading) {
                final DashboardCategory homepageCategory = mCategoryByKeyMap.get(
                        CategoryKey.CATEGORY_HOMEPAGE);
                if (homepageCategory == null) {
                    return;
                }
                for (Tile tile : homepageCategory.getTiles()) {
                    final String key = tile.getKey(context);
                    if (TextUtils.isEmpty(key)) {
                        continue;
                    }
                    HighlightableMenu.addMenuKey(key);
                }
            }
        }
    }

2.7.TileUtils

>可能用到的变量

    public static final String META_DATA_KEY_PROFILE = "com.android.settings.profile";

    public static final String PROFILE_ALL = "all_profiles";
    
    public static final String PROFILE_PRIMARY = "primary_profile_only";

//The key used to get the category from metadata of activities of action EXTRA_SETTINGS_ACTION The value must be from CategoryKey.
    static final String EXTRA_CATEGORY_KEY = "com.android.settings.category";

//应该在AndroidManifest.xml中设置的元数据项的名称,以指定应该用于首选项的键。
    public static final String META_DATA_PREFERENCE_KEYHINT = "com.android.settings.keyhint";   

   //Same as #EXTRA_SETTINGS_ACTION but used for the platform Settings activities.
    private static final String SETTINGS_ACTION = "com.android.settings.action.SETTINGS";

    private static final String OPERATOR_SETTINGS =
            "com.android.settings.OPERATOR_APPLICATION_SETTING";
    private static final String OPERATOR_DEFAULT_CATEGORY =
            "com.android.settings.category.wireless";

    private static final String MANUFACTURER_SETTINGS =
            "com.android.settings.MANUFACTURER_APPLICATION_SETTING";
    private static final String MANUFACTURER_DEFAULT_CATEGORY =
            "com.android.settings.category.device";            

>getCategories

    public static List<DashboardCategory> getCategories(Context context,
            Map<Pair<String, String>, Tile> cache) {
        final long startTime = System.currentTimeMillis();
        final boolean setup =
                Global.getInt(context.getContentResolver(), Global.DEVICE_PROVISIONED, 0) != 0;
        final ArrayList<Tile> tiles = new ArrayList<>();
        final UserManager userManager = (UserManager) context.getSystemService(
                Context.USER_SERVICE);
        for (UserHandle user : userManager.getUserProfiles()) {
         // 只为当前用户添加对应的settings信息
            if (user.getIdentifier() == ActivityManager.getCurrentUser()) {
               //这个只从settings app的清单文件里查找
                loadTilesForAction(context, user, SETTINGS_ACTION, cache, null, tiles, true);
                //这个查找action是OPERATOR_SETTINGS,meta data要查找的key是OPERATOR_DEFAULT_CATEGORY
                loadTilesForAction(context, user, OPERATOR_SETTINGS, cache,
                        OPERATOR_DEFAULT_CATEGORY, tiles, false);
               //
                loadTilesForAction(context, user, MANUFACTURER_SETTINGS, cache,
                        MANUFACTURER_DEFAULT_CATEGORY, tiles, false);
            }
            if (setup) {
                loadTilesForAction(context, user, EXTRA_SETTINGS_ACTION, cache, null, tiles, false);
                loadTilesForAction(context, user, IA_SETTINGS_ACTION, cache, null, tiles, false);
            }
        }
    //根据categoryKey把tile分组,放在map里,
        final HashMap<String, DashboardCategory> categoryMap = new HashMap<>();
        //经过上边的loadTilesForAction方法,我们的tiles里已经填满了数据了
        for (Tile tile : tiles) {
            final String categoryKey = tile.getCategory();
            DashboardCategory category = categoryMap.get(categoryKey);
            if (category == null) {
                category = new DashboardCategory(categoryKey);
                //添加数据
                categoryMap.put(categoryKey, category);
            }
            category.addTile(tile);
        }
        final ArrayList<DashboardCategory> categories = new ArrayList<>(categoryMap.values());
        for (DashboardCategory category : categories) {
            category.sortTiles();
        }
        return categories;
    }

>loadTilesForAction

传递一个intent需要的action,后边查找对应的activity以及provider

    static void loadTilesForAction(Context context,
            UserHandle user, String action, Map<Pair<String, String>, Tile> addedCache,
            String defaultCategory, List<Tile> outTiles, boolean requireSettings) {
        final Intent intent = new Intent(action);
        if (requireSettings) {
        //为true的话,限制intent的包名是settings的包名
            intent.setPackage(SETTING_PKG);
        }
        loadActivityTiles(context, user, addedCache, defaultCategory, outTiles, intent);
        loadProviderTiles(context, user, addedCache, defaultCategory, outTiles, intent);
    }

>loadActivityTiles

根据intent查找相关的activity

    private static void loadActivityTiles(Context context,
            UserHandle user, Map<Pair<String, String>, Tile> addedCache,
            String defaultCategory, List<Tile> outTiles, Intent intent) {
        final PackageManager pm = context.getPackageManager();
        final List<ResolveInfo> results = pm.queryIntentActivitiesAsUser(intent,
                PackageManager.GET_META_DATA, user.getIdentifier());
        for (ResolveInfo resolved : results) {
            if (!resolved.system) {
                //不允许非系统app
                continue;
            }
            final ActivityInfo activityInfo = resolved.activityInfo;
            final Bundle metaData = activityInfo.metaData;
            loadTile(user, addedCache, defaultCategory, outTiles, intent, metaData, activityInfo);
        }
    }

>loadProviderTiles

通过intent查找相关的ContentProvider

    private static void loadProviderTiles(Context context,
            UserHandle user, Map<Pair<String, String>, Tile> addedCache,
            String defaultCategory, List<Tile> outTiles, Intent intent) {
        final PackageManager pm = context.getPackageManager();
        final List<ResolveInfo> results = pm.queryIntentContentProvidersAsUser(intent,
                0 /* flags */, user.getIdentifier());
        for (ResolveInfo resolved : results) {
            if (!resolved.system) {
                //只允许系统app
                continue;
            }
            final ProviderInfo providerInfo = resolved.providerInfo;
            //查找出switch data
            final List<Bundle> switchData = getSwitchDataFromProvider(context,
                    providerInfo.authority);
            if (switchData == null || switchData.isEmpty()) {
                continue;
            }
            for (Bundle metaData : switchData) {
                loadTile(user, addedCache, defaultCategory, outTiles, intent, metaData,
                        providerInfo);
            }
        }
    }

>loadTile

    private static void loadTile(UserHandle user, Map<Pair<String, String>, Tile> addedCache,
            String defaultCategory, List<Tile> outTiles, Intent intent, Bundle metaData,
            ComponentInfo componentInfo) {
        if (user.getIdentifier() != ActivityManager.getCurrentUser()
                && Tile.isPrimaryProfileOnly(componentInfo.metaData)) {
         //非当前用户,或者有 primary_profile_only 标志的,默认标志是all_profiles       
            return;
        }

        String categoryKey = defaultCategory;
        // Load category
        if ((metaData == null || !metaData.containsKey(EXTRA_CATEGORY_KEY))
                && categoryKey == null) {
        //判断下meta data里没有EXTRA_CATEGORY_KEY,并且categoryKey为null
            return;
        } else {
            categoryKey = metaData.getString(EXTRA_CATEGORY_KEY);
        }

    //区分下activity和contentProvider,存储对应的pair数据
        final boolean isProvider = componentInfo instanceof ProviderInfo;
        final Pair<String, String> key = isProvider
                ? new Pair<>(((ProviderInfo) componentInfo).authority,
                        metaData.getString(META_DATA_PREFERENCE_KEYHINT))
                : new Pair<>(componentInfo.packageName, componentInfo.name);
        Tile tile = addedCache.get(key);
        if (tile == null) {
            tile = isProvider
                    ? new ProviderTile((ProviderInfo) componentInfo, categoryKey, metaData)
                    : new ActivityTile((ActivityInfo) componentInfo, categoryKey);
            //数据添加到缓存里
            addedCache.put(key, tile);
        } else {
            tile.setMetaData(metaData);
        }

        if (!tile.userHandle.contains(user)) {
            tile.userHandle.add(user);
        }
        if (!outTiles.contains(tile)) {
        //把最终的tile添加到输出集合里
            outTiles.add(tile);
        }
    }

2.8.Tile

>isPrimaryProfileOnly

根据key读取meta data里的值,如果meta data为空,或者key对应的value为null,这默认为profile_all,这里是判断是否为 prifile primary only

     static boolean isPrimaryProfileOnly(Bundle metaData) {
        String profile = metaData != null
                ? metaData.getString(META_DATA_KEY_PROFILE) : PROFILE_ALL;
        profile = (profile != null ? profile : PROFILE_ALL);
        return TextUtils.equals(profile, PROFILE_PRIMARY);
    }

>hasKey

    public boolean hasKey() {
    //"com.android.settings.keyhint";
        return mMetaData != null && mMetaData.containsKey(META_DATA_PREFERENCE_KEYHINT);
    }

>getKey

获取对应com.android.settings.keyhint的value值

    public String getKey(Context context) {
        if (!hasKey()) {
            return null;
        }
        ensureMetadataNotStale(context);
        if (mMetaData.get(META_DATA_PREFERENCE_KEYHINT) instanceof Integer) {
            return context.getResources().getString(mMetaData.getInt(META_DATA_PREFERENCE_KEYHINT));
        } else {
            return mMetaData.getString(META_DATA_PREFERENCE_KEYHINT);
        }
    }

>hasSwitch

    public static final String META_DATA_PREFERENCE_SWITCH_URI =
            "com.android.settings.switch_uri";
            
    public boolean hasSwitch() {
        return mMetaData != null && mMetaData.containsKey(META_DATA_PREFERENCE_SWITCH_URI);
    }

>getTitle

    public static final String META_DATA_PREFERENCE_TITLE = "com.android.settings.title";
    public static final String META_DATA_PREFERENCE_TITLE_URI =
            "com.android.settings.title_uri";
            
    public CharSequence getTitle(Context context) {
        CharSequence title = null;
        ensureMetadataNotStale(context);
        final PackageManager packageManager = context.getPackageManager();
        //先判断有没有对应的key
        if (mMetaData.containsKey(META_DATA_PREFERENCE_TITLE)) {
            if (mMetaData.containsKey(META_DATA_PREFERENCE_TITLE_URI)) {
                // If has as uri to provide dynamic title, skip loading here. UI will later load
                // at tile binding time.
                return null;
            }
            //获取对应的value值
            if (mMetaData.get(META_DATA_PREFERENCE_TITLE) instanceof Integer) {
                try {
                    final Resources res =
                            packageManager.getResourcesForApplication(mComponentPackage);
                    title = res.getString(mMetaData.getInt(META_DATA_PREFERENCE_TITLE));
                } 
            } else {
                title = mMetaData.getString(META_DATA_PREFERENCE_TITLE);
            }
        }
        //没有设置title值
        if (title == null) {
        //返回组件的label值
            title = getComponentLabel(context);
        }
        return title;
    }

>getSummary

    public static final String META_DATA_PREFERENCE_TITLE = "com.android.settings.title";
    public static final String META_DATA_PREFERENCE_TITLE_URI =
            "com.android.settings.title_uri";
            
    public CharSequence getSummary(Context context) {
        if (mSummaryOverride != null) {
            return mSummaryOverride;
        }
        ensureMetadataNotStale(context);
        CharSequence summary = null;
        final PackageManager packageManager = context.getPackageManager();
        if (mMetaData != null) {
            if (mMetaData.containsKey(META_DATA_PREFERENCE_SUMMARY_URI)) {
            //uri 是通过observer动态处理的,这里先返回空
                return null;
            }
            //meta data里有对应的summary,读取
            if (mMetaData.containsKey(META_DATA_PREFERENCE_SUMMARY)) {
                if (mMetaData.get(META_DATA_PREFERENCE_SUMMARY) instanceof Integer) {
                    try {
                        final Resources res =
                                packageManager.getResourcesForApplication(mComponentPackage);
                        summary = res.getString(mMetaData.getInt(META_DATA_PREFERENCE_SUMMARY));
                    } 
                } else {
                    summary = mMetaData.getString(META_DATA_PREFERENCE_SUMMARY);
                }
            }
        }
        return summary;
    }

2.9.DashboardCategory

这个类里就两个变量,一个是key,一个是tile集合

    public final String key;

    /**
     * List of the category's children
     */
    private List<Tile> mTiles = new ArrayList<>();

    public DashboardCategory(String key) {
        this.key = key;
    }

2.10.TopLevelSettings.java

这个是setting首页用到的Fragment

@SearchIndexable(forTarget = MOBILE)
public class TopLevelSettings extends DashboardFragment implements SplitLayoutListener,
        PreferenceFragmentCompat.OnPreferenceStartFragmentCallback {
        //...
    protected int getPreferenceScreenResId() {
        return R.xml.top_level_settings;
    }

    public void onAttach(Context context) {
        super.onAttach(context);
        HighlightableMenu.fromXml(context, getPreferenceScreenResId());
        //这个controller需要activity,给它传一个
        use(SupportPreferenceController.class).setActivity(getActivity());
    }

    public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
        super.onCreatePreferences(savedInstanceState, rootKey);
        int tintColor = Utils.getHomepageIconColor(getContext());
        iteratePreferences(preference -> {
            Drawable icon = preference.getIcon();
            if (icon != null) {
            //我试着换了个颜色,竟然不生效。
                icon.setTint(tintColor);
            }
        });
    }

>icon.tint

icon.setTint(Color.RED) 改成红色,发现没有效果。
icon.setAlpha(111) 修改透明度,有效果。
把上边修改icon颜色的代码,延迟1秒执行,可以变色,所以应该是其他地方也有修改icon的tint值,全局搜了下setTint,找到了,原来在adapter里又设置过

//mIsEmbeddingActivityEnabled是true,而我们首页也确实是SettingsHomepageActivity
    protected RecyclerView.Adapter onCreateAdapter(PreferenceScreen preferenceScreen) {
        if (!mIsEmbeddingActivityEnabled || !(getActivity() instanceof SettingsHomepageActivity)) {
            return super.onCreateAdapter(preferenceScreen);
        }
        //上边的if条件不满足,所以走这里
        return mHighlightMixin.onCreateAdapter(this, preferenceScreen, mScrollNeeded);
    }

HighlightableTopLevelPreferenceAdapter.java

可以看到,高亮的时候是一种tint,非高亮的时候是一种tint,模拟器测试不支持高亮,所以走的都是removeHighlightBackground方法

    private void addHighlightBackground(PreferenceViewHolder holder) {
        final View v = holder.itemView;
//...
        final Drawable drawable = ((ImageView) v.findViewById(android.R.id.icon)).getDrawable();
        if (drawable != null) {
            drawable.setTint(mIconColorHighlight);
        }
    }

    private void removeHighlightBackground(PreferenceViewHolder holder) {
        final View v = holder.itemView;
//...
        final Drawable drawable = ((ImageView) v.findViewById(android.R.id.icon)).getDrawable();
        if (drawable != null) {
            drawable.setTint(mIconColorNormal);
        }
    }

>onPreferenceStartFragment()

这里是自定义的处理onPreferenceTreeClick事件

    public Fragment getCallbackFragment() {
        return this;
    }
    
    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)
                .setIsSecondLayerPage(true)
                .launch();
        return true;
    }

>>launch()

简单看下launch的方法,可以看到,是固定跳转到SubSettings页面去了。

    public void launch() {
//...
        final Intent intent = toIntent();
//...
        if (launchAsUser && launchForResult) {
            launchForResultAsUser(intent, mLaunchRequest.mUserHandle,
                    mLaunchRequest.mResultListener, mLaunchRequest.mRequestCode);
        } else if (launchAsUser && !launchForResult) {
            launchAsUser(intent, mLaunchRequest.mUserHandle);
        } else if (!launchAsUser && launchForResult) {
            launchForResult(mLaunchRequest.mResultListener, intent, mLaunchRequest.mRequestCode);
        } else {
            launch(intent);
        }
    }

    public Intent toIntent() {
        final Intent intent = new Intent(Intent.ACTION_MAIN);
        copyExtras(intent);
        intent.setClass(mContext, SubSettings.class);
//...
        intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT, mLaunchRequest.mDestinationName);
//...
        return intent;
    }
    void launch(Intent intent) {
        mContext.startActivity(intent);
    }
    

SettingsActivity里会解析intent数据,实例化fragment并显示。

>top_level_settings.xml

如下,可以看到,有的带fragment属性,有的不带,不带的一般都会在Controller里自己处理。

<PreferenceScreen
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:settings="http://schemas.android.com/apk/res-auto"
    android:key="top_level_settings">

    <com.android.settings.widget.HomepagePreference
        android:fragment="com.android.settings.network.NetworkDashboardFragment"
        android:icon="@drawable/ic_settings_wireless"
        android:key="top_level_network"
        android:order="-150"
        android:title="@string/network_dashboard_title"
        android:summary="@string/summary_placeholder"
        settings:highlightableMenuKey="@string/menu_key_network"
        settings:controller="com.android.settings.network.TopLevelNetworkEntryPreferenceController"/>

    <com.android.settings.widget.HomepagePreference
        android:fragment="com.android.settings.connecteddevice.ConnectedDeviceDashboardFragment"
        android:icon="@drawable/ic_devices_other"
        android:key="top_level_connected_devices"
        android:order="-140"
        android:title="@string/connected_devices_dashboard_title"
        android:summary="@string/connected_devices_dashboard_default_summary"
        settings:highlightableMenuKey="@string/menu_key_connected_devices"
        settings:controller="com.android.settings.connecteddevice.TopLevelConnectedDevicesPreferenceController"/>

    <com.android.settings.widget.HomepagePreference
        android:fragment="com.android.settings.applications.AppDashboardFragment"
        android:icon="@drawable/ic_apps"
        android:key="top_level_apps"
        android:order="-130"
        android:title="@string/apps_dashboard_title"
        android:summary="@string/app_and_notification_dashboard_summary"
        settings:highlightableMenuKey="@string/menu_key_apps"/>

3.SettingsHomepageActivity

查下LAUNCHER,如下,可以看到启动页是SettingsHomepageActivity

        <!-- Alias for launcher activity only, as this belongs to each profile. -->
        <activity-alias android:name="Settings"
                android:label="@string/settings_label_launcher"
                android:taskAffinity="com.android.settings.root"
                android:launchMode="singleTask"
                android:exported="true"
                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>

看下activity的onCreate方法.(我们这里只分析phone模式,平板模式的话是另外一套逻辑)

public class SettingsHomepageActivity extends FragmentActivity implements
        CategoryMixin.CategoryHandler {
        //...
        setContentView(R.layout.settings_homepage_container);
        //...
        mMainFragment = showFragment(() -> {
        //可以看到,加载的是TopLevelSettings,2.5小节有这个fragment的介绍
            final TopLevelSettings fragment = new TopLevelSettings();
            fragment.getArguments().putString(SettingsActivity.EXTRA_FRAGMENT_ARG_KEY,
                    highlightMenuKey);
            return fragment;
        }, R.id.main_content);        

3.1.SubSettings

settings首页选项点击以后都会跳转到这个类,具体逻辑见2.5小节的launch方法

public class SubSettings extends SettingsActivity {

    @Override
    public boolean onNavigateUp() {
        finish();
        return true;
    }

    @Override
    protected boolean isValidFragment(String fragmentName) {
        return true;
    }
}

3.2.SettingsActivity

public class SettingsActivity extends SettingsBaseActivity
        implements PreferenceManager.OnPreferenceTreeClickListener,
        PreferenceFragmentCompat.OnPreferenceStartFragmentCallback,
        ButtonBarHandler, FragmentManager.OnBackStackChangedListener {
        //..
    protected void onCreate(Bundle savedState) {
//..
        final Intent intent = getIntent();

        if (shouldShowTwoPaneDeepLink(intent) && tryStartTwoPaneDeepLink(intent)) {
            finish();
            super.onCreate(savedState);
            return;
        }

        super.onCreate(savedState);
       //..
        createUiFromIntent(savedState, intent);
    }        

>createUiFromIntent

    protected void createUiFromIntent(Bundle savedState, Intent intent) {
//..
        final String initialFragmentName = getInitialFragmentName(intent);
//..
        setContentView(R.layout.settings_main_prefs);
//..
        } else {
        //加载fragment
            launchSettingFragment(initialFragmentName, intent);
        }
//..
        // see if we should show Back/Next buttons
        //下边是底部的按钮,设备首次启动,设置默认配置的时候会显示,
        if (intent.getBooleanExtra(EXTRA_PREFS_SHOW_BUTTON_BAR, false)) {

>launchSettingFragment

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

            Bundle initialArguments = intent.getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS);
            //here
            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);
        }
    }

>switchToFragment

根据类名获取实例对象,完事加载到对应的布局里

    private void switchToFragment(String fragmentName, Bundle args, boolean validate,
            int titleResId, CharSequence title) {
        Fragment f = Utils.getTargetFragment(this, fragmentName, args);
        if (f == null) {
            return;
        }
        FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
        transaction.replace(R.id.main_content, f);
        if (titleResId > 0) {
            transaction.setBreadCrumbTitle(titleResId);
        } else if (title != null) {
            transaction.setBreadCrumbTitle(title);
        }
        transaction.commitAllowingStateLoss();
        getSupportFragmentManager().executePendingTransactions();
    }

4. 自定义的preference学习

4.1.HomepagePreference

交给helper类来处理了

public class HomepagePreference extends Preference implements
        HomepagePreferenceLayoutHelper.HomepagePreferenceLayout {

    private final HomepagePreferenceLayoutHelper mHelper;

    public void onBindViewHolder(PreferenceViewHolder holder) {
        super.onBindViewHolder(holder);
        mHelper.onBindViewHolder(holder);
    }

    @Override
    public HomepagePreferenceLayoutHelper getHelper() {
        return mHelper;
    }

>HomepagePreferenceLayoutHelper

主要是设置了要加载的布局,以及图标和文字的可见性处理

/** Helper for homepage preference to manage layout. */
public class HomepagePreferenceLayoutHelper {

//默认加载的布局
    public HomepagePreferenceLayoutHelper(Preference preference) {
        preference.setLayoutResource(R.layout.homepage_preference);
    }
    //设置图标文字的可见性
    void onBindViewHolder(PreferenceViewHolder holder) {
        mIcon = holder.findViewById(R.id.icon_frame);
        mText = holder.findViewById(R.id.text_frame);
        setIconVisible(mIconVisible);
        setIconPaddingStart(mIconPaddingStart);
        setTextPaddingStart(mTextPaddingStart);
    }    

>homepage_preference.xml

左边是图标,右侧是title和summary

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:minHeight="@dimen/homepage_preference_min_height"
    android:gravity="center_vertical"
    android:background="?android:attr/selectableItemBackground"
    android:clipToPadding="false"
    android:baselineAligned="false">

    <LinearLayout
        android:id="@+id/icon_frame"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:minWidth="48dp"
        android:gravity="end|center_vertical"
        android:orientation="horizontal"
        android:paddingStart="@dimen/homepage_preference_icon_padding_start"
        android:paddingTop="4dp"
        android:paddingBottom="4dp">

        <androidx.preference.internal.PreferenceImageView
            android:id="@android:id/icon"
            android:layout_width="24dp"
            android:layout_height="24dp"
            app:maxWidth="48dp"
            app:maxHeight="48dp"/>

    </LinearLayout>

    <RelativeLayout
        android:id="@+id/text_frame"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:paddingTop="16dp"
        android:paddingBottom="16dp"
        android:paddingStart="@dimen/homepage_preference_text_padding_start"
        android:paddingEnd="24dp">

        <TextView
            android:id="@android:id/title"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:singleLine="true"
            android:textAppearance="?android:attr/textAppearanceListItem"
            android:hyphenationFrequency="normalFast"
            android:lineBreakWordStyle="phrase"
            android:ellipsize="marquee"/>

        <TextView
            android:id="@android:id/summary"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_below="@android:id/title"
            android:layout_alignStart="@android:id/title"
            android:layout_gravity="start"
            android:textAlignment="viewStart"
            android:textAppearance="?android:attr/textAppearanceSmall"
            android:textColor="?android:attr/textColorSecondary"
            android:maxLines="4"
            android:hyphenationFrequency="normalFast"
            android:lineBreakWordStyle="phrase"
            style="@style/PreferenceSummaryTextStyle"/>
    </RelativeLayout>
</LinearLayout>

4.2 TwoTargetPreference.java

效果图如下,显示左右两部分,右侧部分需要自定义布局,默认右侧部分是不显示的。 image.png

/**
 * The Base preference with two target areas divided by a vertical divider
 */
public class TwoTargetPreference extends Preference {

    private void init(Context context) {
        setLayoutResource(R.layout.preference_two_target);
//...
        final int secondTargetResId = getSecondTargetResId();
        if (secondTargetResId != 0) {
            setWidgetLayoutResource(secondTargetResId);//这个是Prefrence里的方法
        }
    }
    
    public void onBindViewHolder(PreferenceViewHolder holder) {
//...
        final View divider = holder.findViewById(R.id.two_target_divider);
        final View widgetFrame = holder.findViewById(android.R.id.widget_frame);
        final boolean shouldHideSecondTarget = shouldHideSecondTarget();
        // 提供了右侧布局以后,右侧的容器和divider才会显示
        if (divider != null) {
            divider.setVisibility(shouldHideSecondTarget ? View.GONE : View.VISIBLE);
        }
        if (widgetFrame != null) {
            widgetFrame.setVisibility(shouldHideSecondTarget ? View.GONE : View.VISIBLE);
        }
    }

    protected boolean shouldHideSecondTarget() {
        return getSecondTargetResId() == 0;
    }
//最右侧要添加的布局id
    protected int getSecondTargetResId() {
        return 0;
    }    

>setWidgetLayoutResource()

下边方法是源码PreferenceGroupAdapter.java里的,可以看到我们提供的widget被添加到widgetFrame里去了。

final ViewGroup widgetFrame = view.findViewById(android.R.id.widget_frame);
if (widgetFrame != null) {
    if (descriptor.mWidgetLayoutResId != 0) {
        inflater.inflate(descriptor.mWidgetLayoutResId, widgetFrame);
    } else {
        widgetFrame.setVisibility(View.GONE);
    }
}

>preference_two_target.xml

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:settings="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:minHeight="?android:attr/listPreferredItemHeightSmall"
    android:gravity="center_vertical"
    android:background="@android:color/transparent"
    android:clipToPadding="false">

    <LinearLayout
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:background="?android:attr/selectableItemBackground"
        android:gravity="start|center_vertical"
        android:clipToPadding="false"
        android:paddingStart="?android:attr/listPreferredItemPaddingStart"
        android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">

        <LinearLayout
            android:id="@+id/icon_frame"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:gravity="start|center_vertical"
            android:minWidth="56dp"
            android:orientation="horizontal"
            android:clipToPadding="false"
            android:paddingTop="4dp"
            android:paddingBottom="4dp">
            <androidx.preference.internal.PreferenceImageView
                android:id="@android:id/icon"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                settings:maxWidth="48dp"
                settings:maxHeight="48dp" />
        </LinearLayout>

        <RelativeLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:paddingTop="16dp"
            android:paddingBottom="16dp">

            <TextView
                android:id="@android:id/title"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:singleLine="true"
                android:textAppearance="?android:attr/textAppearanceListItem"
                android:ellipsize="marquee" />

            <TextView
                android:id="@android:id/summary"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_below="@android:id/title"
                android:layout_alignStart="@android:id/title"
                android:textAppearance="?android:attr/textAppearanceListItemSecondary"
                android:textColor="?android:attr/textColorSecondary"
                android:maxLines="10" />

        </RelativeLayout>

    </LinearLayout>

    <include layout="@layout/preference_two_target_divider" />

    <!-- Preference should place its actual preference widget here. -->
    <LinearLayout
        android:id="@android:id/widget_frame"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:minWidth="64dp"
        android:gravity="center"
        android:orientation="vertical" />

</LinearLayout>

4.3. RestrictedPreference.java

这个类在那个settingsLib里,功能由helper类处理。主要作用就是根据功能是否受限,决定是否disable掉这个选项,比如手机没插sim卡,那么那个mobile的功能就是灰色的。

/**
 * Preference class that supports being disabled by a user restriction
 * set by a device admin.
 */
public class RestrictedPreference extends TwoTargetPreference {
    RestrictedPreferenceHelper mHelper;

TwoTargetPreference就是正常的选项卡,左侧是icon,右边是title和summary,最后侧还可能有额外的view

4.4 AddPreference

继承的RestrictedPreference,右侧可以显示一个加号按钮,需要设置加号的点击事件,按钮才能显示

/**
 * A preference with a plus button on the side representing an "add" action. The plus button will
 * only be visible when a non-null click listener is registered.
 */
public class AddPreference extends RestrictedPreference implements View.OnClickListener {

    int getAddWidgetResId() {
        return R.id.add_preference_widget;
    }
    public void setOnAddClickListener(OnAddClickListener listener) {
        mListener = listener;
       if (mWidgetFrame != null) {
           mWidgetFrame.setVisibility(shouldHideSecondTarget() ? View.GONE : View.VISIBLE);
       }
    }    


    protected int getSecondTargetResId() {
        return R.layout.preference_widget_add;
    }

    //可以看到,需要设置listener右侧部分才能显示
    protected boolean shouldHideSecondTarget() {
        return mListener == null;
    }

4.5 RestrictedSwitchPreference

看名字就知道是受限的,也就是有条件的显示。

/**
 * Version of SwitchPreference that can be disabled by a device admin
 * using a user restriction.
 */
public class RestrictedSwitchPreference extends SwitchPreference {


4.6 其他

暂时不看了,系统自定义的有点多。

上边看完了首页的功能,现在继续看详细的功能,挨个看

5. NetworkDashboardFragment

public class NetworkDashboardFragment extends DashboardFragment implements
        MobilePlanPreferenceHost, OnActivityResultListener {
        
    protected int getPreferenceScreenResId() {
        return R.xml.network_provider_internet;
    }
    

6.System选项

    <com.android.settings.widget.HomepagePreference
        android:fragment="com.android.settings.system.SystemDashboardFragment"
        android:icon="@drawable/ic_settings_system_dashboard_white"
        android:key="top_level_system"
        android:order="10"
        android:title="@string/header_category_system"
        android:summary="@string/system_dashboard_summary"
        settings:highlightableMenuKey="@string/menu_key_system"/>

6.1.SystemDashboardFragment

@SearchIndexable
public class SystemDashboardFragment extends DashboardFragment {
//..
    protected int getPreferenceScreenResId() {
        return R.xml.system_dashboard_fragment;
    }
//..

问题描述:上边的xml里有6个preference,还有两个没显示,可最终显示的有7个,那么3个新的哪里来的?

6.2.动态选项

>getCategoryKey

    public String getCategoryKey() {
        return DashboardFragmentRegistry.PARENT_TO_CATEGORY_KEY_MAP.get(getClass().getName());
    }

map里存的是fragment对应的key

        PARENT_TO_CATEGORY_KEY_MAP.put(
                SystemDashboardFragment.class.getName(), CategoryKey.CATEGORY_SYSTEM);

具体的key如下

    public static final String CATEGORY_SYSTEM = "com.android.settings.category.ia.system";

具体逻辑参考2.4小节的refreshDashboardTiles方法,tile数据的获取逻辑看2.7小节,就是根据action以及其他属性查找相关的组件。

>获取到的tile

这里看下settings的清单文件,

  • 查找满足action(com.android.settings.action.SETTINGS)的,有如下2个
  • meta data里是否有key为com.android.settings.category
  • 最终生成2个tile,他们的cagegoryKey都是com.android.settings.category.ia.system
        <activity
            android:name="Settings$UserSettingsActivity"
            android:label="@string/user_settings_title"
            android:exported="true"
            android:icon="@drawable/ic_settings_multiuser">
            <intent-filter android:priority="1">
                <action android:name="android.settings.USER_SETTINGS" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
            <intent-filter>
            //这里
                <action android:name="com.android.settings.action.SETTINGS" />
            </intent-filter>
            <meta-data android:name="com.android.settings.order" android:value="-45"/>
            //这里
            <meta-data android:name="com.android.settings.category"
                       android:value="com.android.settings.category.ia.system" />
            <meta-data android:name="com.android.settings.summary_uri"
                       android:value="content://com.android.settings.dashboard.SummaryProvider/user" />
            <meta-data android:name="com.android.settings.icon"
                       android:resource="@drawable/ic_settings_multiuser" />
            <meta-data android:name="com.android.settings.FRAGMENT_CLASS"
                       android:value="com.android.settings.users.UserSettings" />
            <meta-data android:name="com.android.settings.HIGHLIGHT_MENU_KEY"
                       android:value="@string/menu_key_system"/>
            <meta-data android:name="com.android.settings.PRIMARY_PROFILE_CONTROLLED"
                       android:value="true" />
        </activity>

        <activity
            android:name="Settings$DevelopmentSettingsDashboardActivity"
            android:label="@string/development_settings_title"
            android:icon="@drawable/ic_settings_development"
            android:exported="true"
            android:enabled="false">
            <intent-filter android:priority="1">
                <action android:name="android.settings.APPLICATION_DEVELOPMENT_SETTINGS" />
                <action android:name="com.android.settings.APPLICATION_DEVELOPMENT_SETTINGS" />
                <action android:name="android.service.quicksettings.action.QS_TILE_PREFERENCES"/>
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
            <intent-filter>
            //这里
                <action android:name="com.android.settings.action.SETTINGS" />
            </intent-filter>
            <meta-data android:name="com.android.settings.order" android:value="-40"/>
            //这里
            <meta-data android:name="com.android.settings.category"
                       android:value="com.android.settings.category.ia.system" />
            <meta-data android:name="com.android.settings.summary"
                       android:resource="@string/summary_empty"/>
            <meta-data android:name="com.android.settings.icon"
                       android:resource="@drawable/ic_settings_development" />
            <meta-data android:name="com.android.settings.FRAGMENT_CLASS"
                       android:value="com.android.settings.development.DevelopmentSettingsDashboardFragment" />
            <meta-data android:name="com.android.settings.HIGHLIGHT_MENU_KEY"
                       android:value="@string/menu_key_system"/>
            <meta-data android:name="com.android.settings.PRIMARY_PROFILE_CONTROLLED"
                       android:value="true" />
        </activity>

同样是settings下的,action是 com.android.settings.action.IA_SETTINGS

        <activity android:name=".backup.UserBackupSettingsActivity"
                  android:label="@string/privacy_settings_title"
                  android:exported="true"
                  android:icon="@drawable/ic_settings_backup">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.VOICE_LAUNCH" />
            </intent-filter>
            <!-- 这里,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.system" />
            <meta-data android:name="com.android.settings.icon"
                       android:resource="@drawable/ic_settings_backup" />
            <meta-data android:name="com.android.settings.order" android:value="-60"/>
        </activity>

>tile的使用

具体逻辑看2.4里的refreshDashboardTiles,这里就是具体分析下上边3种tile,如下图,可以看到,红框部分那3个就是动态添加的,其他的几个是xml里有的。

  • com.android.settings.Settings$DevelopmentSettingsDashboardActivity 【Developer options】
  • com.android.settings.Settings$UserSettingsActivity 【Multiple users】
  • com.android.settings.backup.UserBackupSettingsActivity 【Backup】 image.png

key值

meta data里没有配置这个name(com.android.settings.keyhint),所以用的是自动生成的。 dashboard_tile_pref_加上组件名 如下,就是3个动态选项的key值

dashboard_tile_pref_com.android.settings.Settings$DevelopmentSettingsDashboardActivity
dashboard_tile_pref_com.android.settings.Settings$UserSettingsActivity
dashboard_tile_pref_com.android.settings.backup.UserBackupSettingsActivity

具体逻辑上边有贴,再看下DashboardFeatureProviderImpl.java

    public String getDashboardKeyForTile(Tile tile) {
        if (tile == null) {
            return null;
        }
        if (tile.hasKey()) {
            return tile.getKey(mContext);
        }
        //固定前缀
        final StringBuilder sb = new StringBuilder(DASHBOARD_TILE_PREF_KEY_PREFIX);
        final ComponentName component = tile.getIntent().getComponent();
        //加上组件类名
        sb.append(component.getClassName());
        return sb.toString();
    }

title值

meta data里也没有name(com.android.settings.title),所以用的是组件的lable名

android:label="@string/development_settings_title"
android:label="@string/user_settings_title"
android:label="@string/privacy_settings_title"

<string name="privacy_settings_title">Backup</string>

summary值

  • backup 没有summary
  • multiply users给的是uri
            <meta-data android:name="com.android.settings.summary_uri"
                       android:value="content://com.android.settings.dashboard.SummaryProvider/user" />
  • 开发者模式 给的是固定的summary,为空
            <meta-data android:name="com.android.settings.summary"
                       android:resource="@string/summary_empty"/>
    <string name="summary_empty" translatable="false"></string> //在settingsLib包里                       

switch

meta data里没有name为com.android.settings.switch_uri的,所以都是不带开关状态的

icon

            <meta-data android:name="com.android.settings.icon"
                       android:resource="@drawable/ic_settings_development" />
            <meta-data android:name="com.android.settings.icon"
                       android:resource="@drawable/ic_settings_multiuser" />
            <meta-data android:name="com.android.settings.icon"
                       android:resource="@drawable/ic_settings_backup" />

跳转

  • 开发者模式,有设置跳转的fragment
            <meta-data android:name="com.android.settings.FRAGMENT_CLASS"
                       android:value="com.android.settings.development.DevelopmentSettingsDashboardFragment" />
  • 多用户
            <meta-data android:name="com.android.settings.FRAGMENT_CLASS"
                       android:value="com.android.settings.users.UserSettings" />
  • backup 这个没有设置fragment,所以就跳转对应的activity了,如下
        <activity android:name=".backup.UserBackupSettingsActivity"
                  android:label="@string/privacy_settings_title"
                  android:exported="true"
                  android:icon="@drawable/ic_settings_backup">

7.about phone

    <com.android.settings.widget.HomepagePreference
        android:fragment="com.android.settings.deviceinfo.aboutphone.MyDeviceInfoFragment"
        android:icon="@drawable/ic_phone_info"
        android:key="top_level_about_device"
        android:order="20"
        android:title="@string/about_settings"
        android:summary="@string/summary_placeholder"
        settings:highlightableMenuKey="@string/menu_key_about_device"
        settings:controller="com.android.settings.deviceinfo.aboutphone.TopLevelAboutDevicePreferenceController"/>

7.1.MyDeviceInfoFragment

>question

xml里看到的选项位置,和实际显示的有差别,没找到哪里修改的order?

比如下边xml里的header,位置明明在第一,order也是0,实际显示效果却在第二位。

难道order 0不是排在第一?查了下Preference的默认order是int的最大值

public static final int DEFAULT_ORDER = Integer.MAX_VALUE;
    <com.android.settingslib.widget.LayoutPreference
        android:key="my_device_info_header"
        android:order="0"/>

    <PreferenceCategory
        android:key="basic_info_category"
        android:selectable="false"
        android:title="@string/my_device_info_basic_info_category_title">

        <!-- Device name -->
        <com.android.settings.widget.ValidatedEditTextPreference
            android:key="device_name"
            android:order="1"/>

        <!-- Account name -->
        <Preference
            android:key="branded_account"
            android:order="2"/>

        <!-- Phone number -->
        <com.android.settings.deviceinfo.PhoneNumberSummaryPreference
            android:key="phone_number"
            android:order="3"/>
    </PreferenceCategory>

    <PreferenceCategory
        android:title="@string/my_device_info_legal_category_title">

8.开发者属性页面

这个页面是从小节6 里的动态添加的选项跳转过来的,跳转页面如下

            <meta-data android:name="com.android.settings.FRAGMENT_CLASS"
                       android:value="com.android.settings.development.DevelopmentSettingsDashboardFragment" />

8.1.DeveloperOptionsPreferenceController.java

开发者模式下的选项controller基本都继承这个,作用是在下图这个开关关闭的时候统一关闭其他选项

image.png

public abstract class DeveloperOptionsPreferenceController extends AbstractPreferenceController {

    public void displayPreference(PreferenceScreen screen) {
        super.displayPreference(screen);
        mPreference = screen.findPreference(getPreferenceKey());
    }

    public void onDeveloperOptionsEnabled() {
        if (isAvailable()) {
            onDeveloperOptionsSwitchEnabled();
        }
    }

    public void onDeveloperOptionsDisabled() {
        if (isAvailable()) {
            onDeveloperOptionsSwitchDisabled();
        }
    }

    protected void onDeveloperOptionsSwitchEnabled() {
        mPreference.setEnabled(true);
    }

    protected void onDeveloperOptionsSwitchDisabled() {
        mPreference.setEnabled(false);
    }

>开关事件

下边看下fragment里上述开关的切换事件

    private void enableDeveloperOptions() {
        DevelopmentSettingsEnabler.setDevelopmentSettingsEnabled(getContext(), true);
        //所有的controller统一enable
        for (AbstractPreferenceController controller : mPreferenceControllers) {
            if (controller instanceof DeveloperOptionsPreferenceController) {
                ((DeveloperOptionsPreferenceController) controller).onDeveloperOptionsEnabled();
            }
        }
    }

    private void disableDeveloperOptions() {

        DevelopmentSettingsEnabler.setDevelopmentSettingsEnabled(getContext(), false);
        final SystemPropPoker poker = SystemPropPoker.getInstance();
        poker.blockPokes();
        //所有的controller统一disable
        for (AbstractPreferenceController controller : mPreferenceControllers) {
            if (controller instanceof DeveloperOptionsPreferenceController) {
                ((DeveloperOptionsPreferenceController) controller)
                        .onDeveloperOptionsDisabled();
            }
        }
        poker.unblockPokes();
        poker.poke();
    }

8.2.DevelopmentSettingsDashboardFragment

>getPreferenceScreenResId

    protected int getPreferenceScreenResId() {
        return  R.xml.development_settings;
    }

>createPreferenceControllers

    protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
        if (Utils.isMonkeyRunning()) {
            mPreferenceControllers = new ArrayList<>();
            return null;
        }
        //这里添加了上百个controller
        mPreferenceControllers = buildPreferenceControllers(context, getActivity(),
                getSettingsLifecycle(), this /* devOptionsDashboardFragment */,
                new BluetoothA2dpConfigStore());
        return mPreferenceControllers;
    }

>registerReceivers

    private void registerReceivers() {
        LocalBroadcastManager.getInstance(getContext())
                .registerReceiver(mEnableAdbReceiver, new IntentFilter(
                        AdbPreferenceController.ACTION_ENABLE_ADB_STATE_CHANGED));

        final IntentFilter filter = new IntentFilter();
        filter.addAction(BluetoothA2dp.ACTION_CODEC_CONFIG_CHANGED);
        getActivity().registerReceiver(mBluetoothA2dpReceiver, filter);
    }

    private void unregisterReceivers() {
        LocalBroadcastManager.getInstance(getContext()).unregisterReceiver(mEnableAdbReceiver);
        getActivity().unregisterReceiver(mBluetoothA2dpReceiver);
    }
    
    
    private final BroadcastReceiver mEnableAdbReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            for (AbstractPreferenceController controller : mPreferenceControllers) {
                if (controller instanceof AdbOnChangeListener) {
                //这里找到一个,就是8.4那个controller
                    ((AdbOnChangeListener) controller).onAdbSettingChanged();
                }
            }
        }
    };    

8.3.AdbPreferenceController.java

image.png

        <com.android.settingslib.RestrictedSwitchPreference
            android:key="enable_adb"
            android:title="@string/enable_adb"
            android:summary="@string/enable_adb_summary" />

主要传递一个Fragment进来,方便显示对话框,以及处理对话框按钮事件

public class AdbPreferenceController extends AbstractEnableAdbPreferenceController implements
        PreferenceControllerMixin {

    private final DevelopmentSettingsDashboardFragment mFragment;

    public AdbPreferenceController(Context context, DevelopmentSettingsDashboardFragment fragment) {
        super(context);
        mFragment = fragment;
    }

    public void onAdbDialogConfirmed() {
        writeAdbSetting(true);
    }

    public void onAdbDialogDismissed() {
        updateState(mPreference);
    }

    //显示确认对话框,父类调用
    public void showConfirmationDialog(@Nullable Preference preference) {
        EnableAdbWarningDialog.show(mFragment);
    }

>AbstractEnableAdbPreferenceController

public abstract class AbstractEnableAdbPreferenceController extends
        DeveloperOptionsPreferenceController implements ConfirmationDialogController {
    private static final String KEY_ENABLE_ADB = "enable_adb";
    public static final String ACTION_ENABLE_ADB_STATE_CHANGED =

//...
    private boolean isAdbEnabled() {
        final ContentResolver cr = mContext.getContentResolver();
        return Settings.Global.getInt(cr, Settings.Global.ADB_ENABLED, ADB_SETTING_OFF)
                != ADB_SETTING_OFF;
    }
//...
    public boolean handlePreferenceTreeClick(Preference preference) {

        if (TextUtils.equals(KEY_ENABLE_ADB, preference.getKey())) {
            if (!isAdbEnabled()) {
            //不可用的时候点击会弹框确认,子类实现
                showConfirmationDialog(preference);
            } else {
            //关闭的时候不需要确认,直接修改
                writeAdbSetting(false);
            }
            return true;
        } else {
            return false;
        }
    }
//
    protected void writeAdbSetting(boolean enabled) {
        Settings.Global.putInt(mContext.getContentResolver(),
                Settings.Global.ADB_ENABLED, enabled ? ADB_SETTING_ON : ADB_SETTING_OFF);
        notifyStateChanged();
    }
//发送广播,fragment里会监听
    private void notifyStateChanged() {
    //目前搜了下,影响的是8.4那个选项
        LocalBroadcastManager.getInstance(mContext)
                .sendBroadcast(new Intent(ACTION_ENABLE_ADB_STATE_CHANGED));
    }

8.4.VerifyAppsOverUsbPreferenceController.java

image.png

>VerifyAppsOverUsbPreferenceController

public class VerifyAppsOverUsbPreferenceController extends DeveloperOptionsPreferenceController
        implements Preference.OnPreferenceChangeListener, AdbOnChangeListener,
        PreferenceControllerMixin {
        //..

    public void onAdbSettingChanged() {
        if (isAvailable()) {
            updateState(mPreference);
        }
    }
//..
    /**
     * Checks whether the toggle should be enabled depending on whether verify apps over USB is
     * possible currently. If ADB is disabled or if package verifier does not exist, the toggle
     * should be disabled.
     */
    private boolean shouldBeEnabled() {
        final ContentResolver cr = mContext.getContentResolver();
        //先判断usb debugging是否打开,没打开的话这个也不可用
        if (Settings.Global.getInt(cr, Settings.Global.ADB_ENABLED,
                AdbPreferenceController.ADB_SETTING_OFF)
                == AdbPreferenceController.ADB_SETTING_OFF) {
            return false;
        }
        //然后查找有没有可以处理包安装的广播
        final Intent verification = new Intent(Intent.ACTION_PACKAGE_NEEDS_VERIFICATION);
        verification.setType(PACKAGE_MIME_TYPE);
        verification.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
        final List<ResolveInfo> receivers = mPackageManager.queryBroadcastReceivers(
                verification, 0 /* flags */);
        //有的话就可用,没有的话就不可用
        return !receivers.isEmpty();
    }

> 搜到的recevier

action是对的,可惜没有mimeType,所以上边的结果是空。

        <!-- Broadcast Receiver listen to sufficient verifier requests from Package Manager
             when install new SDK, to verifier SDK code during installation time
             and terminate install if SDK not compatible with privacy sandbox restrictions. -->
        <receiver android:name="com.android.server.sdksandbox.SdkSandboxVerifierReceiver"
                 android:exported="false">
            <intent-filter>
                <action android:name="android.intent.action.PACKAGE_NEEDS_VERIFICATION"/>
            </intent-filter>
        </receiver>

x.杂记

x.1 开发者模式

需要用vysor控制设备,所以设备启动以后需要打开usb debug模式。 usb debug模式就算是打开的,重启机器以后也看不到设备,必须关闭再打开才行,所以打开在设备启动以后后台自动模拟这个操作。

>打开关闭开发者模式

DevelopmentSettingsEnabler.java

    public static void setDevelopmentSettingsEnabled(Context context, boolean enable) {
        Settings.Global.putInt(context.getContentResolver(),
                Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, enable ? 1 : 0);
        LocalBroadcastManager.getInstance(context)
                .sendBroadcast(new Intent(DEVELOPMENT_SETTINGS_CHANGED_ACTION));
    }

相关代码可以添加在FallbackHome.java里,这个类也是settings里的

import com.android.settingslib.development.DevelopmentSettingsEnabler;

DevelopmentSettingsEnabler.setDevelopmentSettingsEnabled(this,true);

>usb debug开关

来源可以看8.3

Settings.Global.putInt(mContext.getContentResolver(),
                Settings.Global.ADB_ENABLED, enabled ? 1 : 0);

>开机后自动打开开发者模式以及usb debug

在 FallbackHome.java里加入如下的代码

import com.android.settingslib.development.DevelopmentSettingsEnabler;

    DevelopmentSettingsEnabler.setDevelopmentSettingsEnabled(this,true);
    Settings.Global.putInt(getContentResolver(),Settings.Global.ADB_ENABLED, 1);

.总结

  • 核心基类DashboardFragment,静态数据(xml里加载),动态数据(Tiles)
  • 条目的内容,基本都是靠controller来控制的,
  • tile有两种数据来源,一种是activity的,一种是contentProvider的,最终生成的preference是switchPreference或者普通的Preference
  • TileUtils 是用来获取所有的Tile数据的
全部评论

相关推荐

04-15 09:59
门头沟学院 C++
yy_11:小公司人家没必要泄密,大公司都是本地部署了
你想吐槽公司的哪些规定
点赞 评论 收藏
分享
05-27 20:40
已编辑
天津师范大学 Java
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务